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.FeatureActionRecharge": "Action Recharge",
"SW5E.Flaws": "Flaws", "SW5E.Flaws": "Flaws",
"SW5E.EffectCreate": "Create Effect",
"SW5E.EffectToggle": "Toggle Effect",
"SW5E.EffectEdit": "Edit Effect",
"SW5E.EffectDelete": "Delete Effect",
"SW5E.ItemTypeArchetype": "Archetype", "SW5E.ItemTypeArchetype": "Archetype",
"SW5E.ItemTypeBackground": "Background", "SW5E.ItemTypeBackground": "Background",
"Sw5E.ItemTypeBackgroundPl": "Backgrounds", "Sw5E.ItemTypeBackgroundPl": "Backgrounds",
@ -332,6 +337,12 @@
"SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.", "SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
"SW5E.FlagsCritThreshold": "Critical Hit Threshold", "SW5E.FlagsCritThreshold": "Critical Hit Threshold",
"SW5E.FlagsCritThresholdHint": "Allow for expanded critical range; for example Improved or Superior Critical", "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.Flat": "Flat",
"SW5E.Formula": "Formula", "SW5E.Formula": "Formula",
@ -582,6 +593,17 @@
"SW5E.RollMode": "Roll Mode", "SW5E.RollMode": "Roll Mode",
"SW5E.RollSituationalBonus": "Situational Bonus?", "SW5E.RollSituationalBonus": "Situational Bonus?",
"SW5E.Save": "Save", "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.SheetClassCharacter": "Default Character Sheet",
"SW5E.SheetClassCharacterOld": "Old Character Sheet", "SW5E.SheetClassCharacterOld": "Old Character Sheet",
"SW5E.SheetClassNPC": "Default NPC Sheet", "SW5E.SheetClassNPC": "Default NPC Sheet",
@ -837,6 +859,7 @@
"SW5E.available": "available", "SW5E.available": "available",
"SW5E.description": "A comprehensive game system for running games of Star Wars 5th Edition in the Foundry VTT environment.", "SW5E.description": "A comprehensive game system for running games of Star Wars 5th Edition in the Foundry VTT environment.",
"SW5E.of": "of", "SW5E.of": "of",
"SW5E.per": "per",
"SW5E.power": "power", "SW5E.power": "power",
"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.", "SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.",
"SETTINGS.5eAllowPolymorphingN": "Allow Polymorphing", "SETTINGS.5eAllowPolymorphingN": "Allow Polymorphing",

View file

@ -90,6 +90,7 @@
.russoOne(14px); .russoOne(14px);
color: @colorOlive; color: @colorOlive;
border-bottom: 1px solid @colorFaint; border-bottom: 1px solid @colorFaint;
white-space: nowrap;
} }
/* ----------------------------------------- */ /* ----------------------------------------- */
@ -142,6 +143,7 @@
font-family: "Signika", sans-serif; font-family: "Signika", sans-serif;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
white-space: nowrap;
} }
} }
} }
@ -413,46 +415,18 @@
} }
} }
// Inventory item lists
.inventory-list { .inventory-list {
list-style: none;
margin: 0;
padding: 0 5px; padding: 0 5px;
overflow-y: auto;
scrollbar-width: thin;
color: @colorTan;
// Inventory Item
.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 { .item-name {
cursor: pointer; 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 { &.rollable:hover .item-image {
background-image: url("../../icons/svg/d20-grey.svg") !important; background-image: url("../../icons/svg/d20-grey.svg") !important;
} }
&.rollable .item-image:hover { &.rollable .item-image:hover {
background-image: url("../../icons/svg/d20-black.svg") !important; background-image: url("../../icons/svg/d20-black.svg") !important;
} }
i.attuned { i.attuned {
color: @colorTan; color: @colorTan;
} }
@ -474,49 +448,26 @@
flex: 0 0 80px; flex: 0 0 80px;
text-align: right; text-align: right;
font-size: 11px; font-size: 11px;
color: @colorTan;
white-space: nowrap; white-space: nowrap;
} }
} }
// Inventory Header // Inventory Header
.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 { .item-controls a.item-create {
flex: 0 0 100%; flex: 0 0 100%;
} }
} }
// Item names
.item-name {
color: @colorDark;
}
// Item Detail Sections // Item Detail Sections
.item-detail { .item-detail {
flex: 0 0 70px; flex: 0 0 70px;
font-size: 12px; font-size: 12px;
color: @colorTan;
text-align: center; text-align: center;
border-right: 1px solid @colorFaint; border-right: 1px solid @colorFaint;
word-break: break-word; word-break: break-word;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
&:last-child { border-right: none; } &:last-child { border-right: none; }
&.item-action {flex: 0 0 100px} &.item-action {flex: 0 0 100px}
} }
@ -527,24 +478,9 @@
border-right: 1px solid @colorFaint; border-right: 1px solid @colorFaint;
} }
.item-list {
list-style: none;
margin: 0;
padding: 0;
}
// Item Control Buttons // Item Control Buttons
.item-controls { .item-controls {
flex: 0 0 44px; 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 // Item Dropdown Summary
@ -553,6 +489,7 @@
font-size: 12px; font-size: 12px;
line-height: 16px; line-height: 16px;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
color: @colorDark;
border-top: 1px solid @colorFaint; border-top: 1px solid @colorFaint;
} }
} }
@ -693,44 +630,6 @@
// Empty powerbook controls // Empty powerbook controls
.powerbook-empty .item-controls { flex: 1; } .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 */ /* TinyMCE */
/* ----------------------------------------- */ /* ----------------------------------------- */
@ -739,3 +638,18 @@
padding: 0 8px; 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; 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-group.uses-per {
.form-fields {
flex-wrap: nowrap;
}
input { input {
flex: 1; flex: 0 0 32px;
} }
span { span {
flex: 0 0 16px; flex: 0 0 16px;
} margin: 0 4px 0 0;
select {
flex: 3;
} }
} }
span.sep { span.sep {
flex: 0 0 8px; flex: 0 0 8px;
} }

View file

@ -6,7 +6,7 @@ import AbilityTemplate from "../pixi/ability-template.js";
import {SW5E} from '../config.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 { 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 */ /** @override */
prepareBaseData() { prepareBaseData() {
switch ( this.data.type ) { switch ( this.data.type ) {
@ -116,6 +74,11 @@ export default class Actor5e extends Actor {
abl.save = Math.max(abl.save, originalSaves[id].save); 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); this._prepareSkills(actorData, bonuses, checkBonus, originalSkills);
// Determine Initiative Modifier // Determine Initiative Modifier
@ -126,7 +89,8 @@ export default class Actor5e extends Actor {
if ( joat ) init.prof = Math.floor(0.5 * data.attributes.prof); if ( joat ) init.prof = Math.floor(0.5 * data.attributes.prof);
else if ( athlete ) init.prof = Math.ceil(0.5 * data.attributes.prof); else if ( athlete ) init.prof = Math.ceil(0.5 * data.attributes.prof);
else init.prof = 0; 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; init.total = init.mod + init.prof + init.bonus;
// Prepare power-casting data // Prepare power-casting data
@ -169,7 +133,7 @@ export default class Actor5e extends Actor {
} }
return obj; return obj;
}, {}); }, {});
data.prof = this.data.data.attributes.prof; data.prof = this.data.data.attributes.prof || 0;
return data; return data;
} }
@ -177,33 +141,36 @@ export default class Actor5e extends Actor {
/** /**
* Return the features which a character is awarded for each class level * 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 * @return {Promise<Item5e[]>} Array of Item5e entities
*/ */
static async getClassFeatures(cls) { static async getClassFeatures({className="", subclassName="", level=1, priorLevel=0}={}) {
const level = cls.data.levels; className = className.toLowerCase();
const className = cls.name.toLowerCase(); subclassName = subclassName.slugify();
// Get the configuration of features which may be added // Get the configuration of features which may be added
const clsConfig = CONFIG.SW5E.classFeatures[className]; const clsConfig = CONFIG.SW5E.classFeatures[className];
if (!clsConfig) return []; if (!clsConfig) return [];
let featureIDs = clsConfig["features"][level] || [];
const subclassName = cls.data.subclass.toLowerCase().slugify();
// Identify subclass features // Acquire class features
if ( subclassName !== "" ) { let ids = [];
const subclassConfig = clsConfig["subclasses"][subclassName]; for ( let [l, f] of Object.entries(clsConfig.features || {}) ) {
if ( subclassConfig !== undefined ) { l = parseInt(l);
const subclassFeatureIDs = subclassConfig["features"][level]; if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
if ( subclassFeatureIDs ) { }
featureIDs = featureIDs.concat(subclassFeatureIDs);
} // Acquire subclass features
} const subConfig = clsConfig.subclasses[subclassName] || {};
else console.warn("Invalid subclass: " + 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 // 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 // Class powers should always be prepared
for ( const feature of features ) { for ( const feature of features ) {
@ -237,35 +204,28 @@ export default class Actor5e extends Actor {
for (let u of updated instanceof Array ? updated : [updated]) { for (let u of updated instanceof Array ? updated : [updated]) {
const item = this.items.get(u._id); const item = this.items.get(u._id);
if (!item || (item.data.type !== "class")) continue; if (!item || (item.data.type !== "class")) continue;
const classData = duplicate(item.data); const updateData = expandObject(u);
let changed = false; 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 // Get and create features for an increased class level
const newLevels = getProperty(u, "data.levels"); let changed = false;
if (newLevels && (newLevels > item.data.data.levels)) { if ( config.level && (config.level > config.priorLevel)) changed = true;
classData.data.levels = newLevels; if ( config.subclassName !== item.data.data.subclass ) changed = true;
changed = true;
}
// Get features for a newly changed subclass // Get features to create
const newSubclass = getProperty(u, "data.subclass");
if (newSubclass && (newSubclass !== item.data.data.subclass)) {
classData.data.subclass = newSubclass;
changed = true;
}
// Get the new features
if ( changed ) { if ( changed ) {
const features = await Actor5e.getClassFeatures(classData); const existing = new Set(this.items.map(i => i.name));
if ( features.length ) toCreate.push(...features); const features = await Actor5e.getClassFeatures(config);
for ( let f of features ) {
if ( !existing.has(f.name) ) toCreate.push(f);
}
} }
} }
// 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));
}
return toCreate return toCreate
} }
@ -301,9 +261,6 @@ export default class Actor5e extends Actor {
const required = xp.max - prior; const required = xp.max - prior;
const pct = Math.round((xp.value - prior) * 100 / required); const pct = Math.round((xp.value - prior) * 100 / required);
xp.pct = Math.clamped(pct, 0, 100); 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; 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 // Compute modifier
skl.bonus = checkBonus + skillBonus; skl.bonus = checkBonus + skillBonus;
skl.mod = data.abilities[skl.ability].mod; skl.mod = data.abilities[skl.ability].mod;
skl.prof = round(multi * data.attributes.prof); skl.prof = round(multi * data.attributes.prof);
skl.total = skl.mod + skl.prof + skl.bonus; 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 // Compute passive bonus
const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0; const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0;
skl.passive = 10 + skl.total + passive; 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)); 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); powers.pact.value = Math.min(powers.pact.value, powers.pact.max);
} else { } else {
powers.pact.level = 0; powers.pact.max = parseInt(powers.pact.override) || 0
powers.pact.max = 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; if ( !physicalItems.includes(i.type) ) return weight;
const q = i.data.quantity || 0; const q = i.data.quantity || 0;
const w = i.data.weight || 0; const w = i.data.weight || 0;
return weight + Math.round(q * w * 10) / 10; return weight + (q * w);
}, 0); }, 0);
// [Optional] add Currency Weight // [Optional] add Currency Weight
if ( game.settings.get("sw5e", "currencyWeight") ) { if ( game.settings.get("sw5e", "currencyWeight") ) {
const currency = actorData.data.currency; const currency = actorData.data.currency;
const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0); 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 // 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); if ( this.getFlag("sw5e", "powerfulBuild") ) mod = Math.min(mod * 2, 8);
// Compute Encumbrance percentage // Compute Encumbrance percentage
weight = weight.toNearest(0.1);
const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod; const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod;
const pct = Math.clamped((weight* 100) / max, 0, 100); const pct = Math.clamped((weight * 100) / max, 0, 100);
return { value: weight, max, pct, encumbered: pct > (2/3) }; return { value: weight.toNearest(0.1), max, pct, encumbered: pct > (2/3) };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -564,9 +522,6 @@ export default class Actor5e extends Actor {
/** @override */ /** @override */
async update(data, options={}) { 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 // Apply changes in Actor size to Token width/height
const newSize = getProperty(data, "data.traits.size"); const newSize = getProperty(data, "data.traits.size");
if ( newSize && (newSize !== getProperty(this.data, "data.traits.size")) ) { if ( newSize && (newSize !== getProperty(this.data, "data.traits.size")) ) {
@ -577,7 +532,7 @@ export default class Actor5e extends Actor {
data["token.width"] = size; data["token.width"] = size;
} }
} }
// Reset death save counters // Reset death save counters
if ( (this.data.data.attributes.hp.value <= 0) && (getProperty(data, "data.attributes.hp.value") > 0) ) { if ( (this.data.data.attributes.hp.value <= 0) && (getProperty(data, "data.attributes.hp.value") > 0) ) {
setProperty(data, "data.attributes.death.success", 0); setProperty(data, "data.attributes.death.success", 0);
@ -858,7 +813,7 @@ export default class Actor5e extends Actor {
rollAbilitySave(abilityId, options={}) { rollAbilitySave(abilityId, options={}) {
const label = CONFIG.SW5E.abilities[abilityId]; const label = CONFIG.SW5E.abilities[abilityId];
const abl = this.data.data.abilities[abilityId]; const abl = this.data.data.abilities[abilityId];
// Construct parts // Construct parts
const parts = ["@mod"]; const parts = ["@mod"];
const data = {mod: abl.mod}; const data = {mod: abl.mod};
@ -937,7 +892,7 @@ export default class Actor5e extends Actor {
// Take action depending on the result // Take action depending on the result
const success = roll.total >= 10; const success = roll.total >= 10;
const d20 = roll.dice[0].total; const d20 = roll.dice[0].total;
// Save success // Save success
if ( success ) { if ( success ) {
@ -1429,14 +1384,16 @@ export default class Actor5e extends Actor {
if ( !original ) return; if ( !original ) return;
// Get the Tokens which represent this actor // Get the Tokens which represent this actor
const tokens = this.getActiveTokens(true); if ( canvas.ready ) {
const tokenUpdates = tokens.map(t => { const tokens = this.getActiveTokens(true);
const tokenData = duplicate(original.data.token); const tokenUpdates = tokens.map(t => {
tokenData._id = t.id; const tokenData = duplicate(original.data.token);
tokenData.actorId = original.id; tokenData._id = t.id;
return tokenData; tokenData.actorId = original.id;
}); return tokenData;
canvas.scene.updateEmbeddedEntity("Token", tokenUpdates); });
canvas.scene.updateEmbeddedEntity("Token", tokenUpdates);
}
// Delete the polymorphed Actor and maybe re-render the original sheet // Delete the polymorphed Actor and maybe re-render the original sheet
const isRendered = this.sheet.rendered; const isRendered = this.sheet.rendered;

View file

@ -1,10 +1,12 @@
import Item5e from "../../item/entity.js"; import Item5e from "../../item/entity.js";
import TraitSelector from "../../apps/trait-selector.js"; import TraitSelector from "../../apps/trait-selector.js";
import ActorSheetFlags from "../../apps/actor-flags.js"; import ActorSheetFlags from "../../apps/actor-flags.js";
import MovementConfig from "../../apps/movement-config.js";
import {SW5E} from '../../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. * This sheet is an Abstract layer which is not used.
* @extends {ActorSheet} * @extends {ActorSheet}
*/ */
@ -94,6 +96,9 @@ export default class ActorSheet5e extends ActorSheet {
} }
} }
// Movement speeds
data.movement = this._getMovementSpeed(data.actor);
// Update traits // Update traits
this._prepareTraits(data.actor.data.traits); this._prepareTraits(data.actor.data.traits);
@ -101,7 +106,7 @@ export default class ActorSheet5e extends ActorSheet {
this._prepareItems(data); this._prepareItems(data);
// Prepare active effects // Prepare active effects
this._prepareEffects(data); data.effects = prepareActiveEffectCategories(this.entity.effects);
// Return data to the sheet // Return data to the sheet
return data 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 * 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 * @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 * Insert a power into the powerbook object when rendering the character sheet
* @param {Object} data The Actor data being prepared * @param {Object} data The Actor data being prepared
@ -242,7 +232,7 @@ export default class ActorSheet5e extends ActorSheet {
registerSection(sl, lvl, CONFIG.SW5E.powerLevels[lvl], levels[sl]); registerSection(sl, lvl, CONFIG.SW5E.powerLevels[lvl], levels[sl]);
} }
} }
// Pact magic users have cantrips and a pact magic section // Pact magic users have cantrips and a pact magic section
if ( levels.pact && levels.pact.max ) { if ( levels.pact && levels.pact.max ) {
if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
@ -363,7 +353,7 @@ export default class ActorSheet5e extends ActorSheet {
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this)); filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
// Item summaries // Item summaries
html.find('.item .item-name h4').click(event => this._onItemSummary(event)); html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
// Editable Only Listeners // Editable Only Listeners
if ( this.isEditable ) { if ( this.isEditable ) {
@ -383,6 +373,7 @@ export default class ActorSheet5e extends ActorSheet {
html.find('.trait-selector').click(this._onTraitSelector.bind(this)); html.find('.trait-selector').click(this._onTraitSelector.bind(this));
// Configure Special Flags // Configure Special Flags
html.find('.configure-movement').click(this._onMovementConfig.bind(this));
html.find('.configure-flags').click(this._onConfigureFlags.bind(this)); html.find('.configure-flags').click(this._onConfigureFlags.bind(this));
// Owned Item management // Owned Item management
@ -393,8 +384,7 @@ export default class ActorSheet5e extends ActorSheet {
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
// Active Effect management // 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 // Owner Only Listeners
@ -565,7 +555,7 @@ export default class ActorSheet5e extends ActorSheet {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
@ -576,9 +566,7 @@ export default class ActorSheet5e extends ActorSheet {
} }
// Create the owned item as normal // Create the owned item as normal
// TODO remove conditional logic in 0.7.x return super._onDropItemCreate(itemData);
if (isNewerVersion(game.data.version, "0.6.9")) return super._onDropItemCreate(itemData);
else return this.actor.createEmbeddedEntity("OwnedItem", 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 * Handle rolling an Ability check, either a test or a saving throw
* @param {Event} event The originating click event * @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 */ /** @override */
_getHeaderButtons() { _getHeaderButtons() {
let buttons = super._getHeaderButtons(); let buttons = super._getHeaderButtons();
@ -839,90 +817,4 @@ export default class ActorSheet5e extends ActorSheet {
}); });
return buttons; 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

@ -68,7 +68,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} }, backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} },
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} } loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
}; };
// Partition items by category // Partition items by category
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => { let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
@ -109,7 +109,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
for ( let i of items ) { for ( let i of items ) {
i.data.quantity = i.data.quantity || 0; i.data.quantity = i.data.quantity || 0;
i.data.weight = i.data.weight || 0; i.data.weight = i.data.weight || 0;
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10; i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1);
inventory[i.type].items.push(i); inventory[i.type].items.push(i);
} }
@ -123,12 +123,12 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
const features = { const features = {
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true }, classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: false, dataset: {type: "classfeature"}, isClassfeature: true }, classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: false, dataset: {type: "classfeature"}, isClassfeature: true },
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true },
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true }, species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true },
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true }, background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true },
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true },
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true }, fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true },
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true }, lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true },
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} } passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
}; };
@ -138,13 +138,13 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
} }
classes.sort((a, b) => b.levels - a.levels); classes.sort((a, b) => b.levels - a.levels);
features.classes.items = classes; features.classes.items = classes;
features.classfeatures.items = classfeatures; features.classfeatures.items = classfeatures;
features.archetype.items = archetypes; features.archetype.items = archetypes;
features.species.items = species; features.species.items = species;
features.background.items = backgrounds; features.background.items = backgrounds;
features.fightingstyles.items = fightingstyles; features.fightingstyles.items = fightingstyles;
features.fightingmasteries.items = fightingmasteries; features.fightingmasteries.items = fightingmasteries;
features.lightsaberforms.items = lightsaberforms; features.lightsaberforms.items = lightsaberforms;
// Assign and return // Assign and return
data.inventory = Object.values(inventory); data.inventory = Object.values(inventory);
@ -189,9 +189,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
super.activateListeners(html); super.activateListeners(html);
if ( !this.options.editable ) return; if ( !this.options.editable ) return;
// Inventory Functions
html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
// Item State Toggling // Item State Toggling
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find('.item-toggle').click(this._onToggleItem.bind(this));
@ -199,8 +196,8 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
html.find('.short-rest').click(this._onShortRest.bind(this)); html.find('.short-rest').click(this._onShortRest.bind(this));
html.find('.long-rest').click(this._onLongRest.bind(this)); html.find('.long-rest').click(this._onLongRest.bind(this));
// Death saving throws // Rollable sheet actions
html.find('.death-save').click(this._onDeathSave.bind(this)); 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 * @param {MouseEvent} event The originating click event
* @private * @private
*/ */
_onDeathSave(event) { _onSheetAction(event) {
event.preventDefault(); event.preventDefault();
return this.actor.rollDeathSave({event: event}); 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 * Handle toggling the state of an Owned Item within the Actor
* @param {Event} event The triggering click event * @param {Event} event The triggering click event
@ -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 */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
let addLevel = false;
// Upgrade the number of class levels a character has and add features // Upgrade the number of class levels a character has and add features
if ( itemData.type === "class" ) { if ( itemData.type === "class" ) {
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name); const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
const classWasAlreadyPresent = !!cls; let priorLevel = cls?.data.data.levels ?? 0;
const hasClass = !!cls;
// Add new features for class level // Increment levels instead of creating a new item
if ( !classWasAlreadyPresent ) { if ( hasClass ) {
Actor5e.getClassFeatures(itemData).then(features => { const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
this.actor.createEmbeddedEntity("OwnedItem", features); if ( next > priorLevel ) {
}); itemData.levels = next;
await cls.update({"data.levels": next});
addLevel = true;
}
} }
// If the actor already has the class, increment the level instead of creating a new item // Add class features
// then add new features as long as level increases if ( !hasClass || addLevel ) {
if ( classWasAlreadyPresent ) { const features = await Actor5e.getClassFeatures({
const lvl = cls.data.data.levels; className: itemData.name,
const newLvl = Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level); subclassName: itemData.data.subclass,
if ( !(lvl === newLvl) ) { level: itemData.levels,
cls.update({"data.levels": newLvl}); priorLevel: priorLevel
itemData.data.levels = newLvl; });
Actor5e.getClassFeatures(itemData).then(features => { await this.actor.createEmbeddedEntity("OwnedItem", features);
this.actor.createEmbeddedEntity("OwnedItem", features);
});
}
return
} }
} }
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 */ /** @override */
activateListeners(html) { activateListeners(html) {
super.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 * @param {Event} event The original click event
* @private * @private
*/ */
_onRollHealthFormula(event) { _onRollHPFormula(event) {
event.preventDefault(); event.preventDefault();
const formula = this.actor.data.data.attributes.hp.formula; const formula = this.actor.data.data.attributes.hp.formula;
if ( !formula ) return; if ( !formula ) return;

View file

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

View file

@ -70,7 +70,7 @@ export default class AbilityUseDialog extends Dialog {
dlg.render(true); dlg.render(true);
}); });
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Helpers */ /* Helpers */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

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

@ -36,7 +36,7 @@ export default class ShortRestDialog extends Dialog {
/** @override */ /** @override */
getData() { getData() {
const data = super.getData(); const data = super.getData();
// Determine Hit Dice // Determine Hit Dice
data.availableHD = this.actor.data.items.reduce((hd, item) => { data.availableHD = this.actor.data.items.reduce((hd, item) => {
if ( item.type === "class" ) { if ( item.type === "class" ) {
@ -49,7 +49,7 @@ export default class ShortRestDialog extends Dialog {
}, {}); }, {});
data.canRoll = this.actor.data.data.attributes.hd > 0; data.canRoll = this.actor.data.data.attributes.hd > 0;
data.denomination = this._denom; data.denomination = this._denom;
// Determine rest type // Determine rest type
const variant = game.settings.get("sw5e", "restVariant"); const variant = game.settings.get("sw5e", "restVariant");
data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute

View file

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

View file

@ -27,41 +27,14 @@ export const _getInitiativeFormula = function(combatant) {
return parts.filter(p => p !== null).join(" + "); return parts.filter(p => p !== null).join(" + ");
}; };
/* -------------------------------------------- */
/** /**
* TODO: A temporary shim until 0.7.x becomes stable * When the Combat encounter updates - re-render open Actor sheets for combatants in the encounter.
* @override
*/ */
TokenConfig.getTrackedAttributes = function(data, _path=[]) { Hooks.on("updateCombat", (combat, data, options, userId) => {
const updateTurn = ("turn" in data) || ("round" in data);
// Track the path and record found attributes if ( !updateTurn ) return;
const attributes = { for ( let t of combat.turns ) {
"bar": [], const a = t.actor;
"value": [] if ( t.actor ) t.actor.sheet.render(false);
};
// 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);
}
}
// 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" "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 = { SW5E.distanceUnits = {
"none": "SW5E.None", "none": "SW5E.None",
"self": "SW5E.DistSelf", "self": "SW5E.DistSelf",
"touch": "SW5E.DistTouch", "touch": "SW5E.DistTouch",
"ft": "SW5E.DistFt",
"mi": "SW5E.DistMi",
"spec": "SW5E.Special", "spec": "SW5E.Special",
"any": "SW5E.DistAny" "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 * Enumerate the denominations of hit dice which can apply to classes in the SW5E system
* @type {Array.<string>} * @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 = { SW5E.powerPreparationModes = {
"prepared": "SW5E.PowerPrepPrepared",
"always": "SW5E.PowerPrepAlways", "always": "SW5E.PowerPrepAlways",
"atwill": "SW5E.PowerPrepAtWill", "atwill": "SW5E.PowerPrepAtWill",
"innate": "SW5E.PowerPrepInnate", "innate": "SW5E.PowerPrepInnate"
"prepared": "SW5E.PowerPrepPrepared"
}; };
SW5E.powerUpcastModes = ["always", "pact", "prepared"]; SW5E.powerUpcastModes = ["always", "pact", "prepared"];
SW5E.powerProgression = { SW5E.powerProgression = {
"none": "SW5E.PowerNone", "none": "SW5E.PowerNone",
"full": "SW5E.PowerProgFull", "full": "SW5E.PowerProgFull",
@ -901,15 +914,27 @@ SW5E.characterFlags = {
type: Boolean type: Boolean
}, },
"weaponCriticalThreshold": { "weaponCriticalThreshold": {
name: "SW5E.FlagsCritThreshold", name: "SW5E.FlagsWeaponCritThreshold",
hint: "SW5E.FlagsCritThresholdHint", hint: "SW5E.FlagsWeaponCritThresholdHint",
section: "Feats", section: "Feats",
type: Number, type: Number,
placeholder: 20 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 // Configure allowed status flags
SW5E.allowedActorFlags = [ SW5E.allowedActorFlags = ["isPolymorphed", "originalActor"].concat(Object.keys(SW5E.characterFlags));
"isPolymorphed", "originalActor"
].concat(Object.keys(SW5E.characterFlags));

View file

@ -1,9 +1,9 @@
/** /**
* A standardized helper function for managing core 5e "d20 rolls" * A standardized helper function for managing core 5e "d20 rolls"
* *
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward". * Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
* This chooses the default options of a normal attack with no bonus, Advantage, or Disadvantage respectively * This chooses the default options of a normal attack with no bonus, Advantage, or Disadvantage respectively
* *
* @param {Array} parts The dice roll component parts, excluding the initial d20 * @param {Array} parts The dice roll component parts, excluding the initial d20
* @param {Object} data Actor or item data against which to parse the roll * @param {Object} data Actor or item data against which to parse the roll
* @param {Event|object} event The triggering event which initiated the roll * @param {Event|object} event The triggering event which initiated the roll
@ -27,72 +27,72 @@
* @param {object} messageData Additional data which is applied to the created Chat Message, if any * @param {object} messageData Additional data which is applied to the created Chat Message, if any
* *
* @return {Promise} A Promise which resolves once the roll workflow has completed * @return {Promise} A Promise which resolves once the roll workflow has completed
*/ */
export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null, export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null,
flavor=null, fastForward=null, dialogOptions, flavor=null, fastForward=null, dialogOptions,
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null, advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null,
elvenAccuracy=false, halflingLucky=false, reliableTalent=false, elvenAccuracy=false, halflingLucky=false, reliableTalent=false,
chatMessage=true, messageData={}}={}) { chatMessage=true, messageData={}}={}) {
// Prepare Message Data // Prepare Message Data
messageData.flavor = flavor || title; messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker(); messageData.speaker = speaker || ChatMessage.getSpeaker();
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")}; const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
parts = parts.concat(["@bonus"]); parts = parts.concat(["@bonus"]);
// Handle fast-forward events // Handle fast-forward events
let adv = 0; let adv = 0;
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
if (fastForward) { if (fastForward) {
if ( advantage || event.altKey ) adv = 1; if ( advantage || event.altKey ) adv = 1;
else if ( disadvantage || event.ctrlKey || event.metaKey ) adv = -1; else if ( disadvantage || event.ctrlKey || event.metaKey ) adv = -1;
}
// Define the inner roll function
const _roll = (parts, adv, form) => {
// Determine the d20 roll and modifiers
let nd = 1;
let mods = halflingLucky ? "r=1" : "";
// Handle advantage
if (adv === 1) {
nd = elvenAccuracy ? 3 : 2;
messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true;
mods += "kh";
} }
// Define the inner roll function
const _roll = (parts, adv, form) => {
// Determine the d20 roll and modifiers
let nd = 1;
let mods = halflingLucky ? "r=1" : "";
// Handle advantage // Handle disadvantage
if (adv === 1) { else if (adv === -1) {
nd = elvenAccuracy ? 3 : 2; nd = 2;
messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`; messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true; if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true;
mods += "kh"; mods += "kl";
} }
// Handle disadvantage
else if (adv === -1) {
nd = 2;
messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true;
mods += "kl";
}
// Prepend the d20 roll // Prepend the d20 roll
let formula = `${nd}d20${mods}`; let formula = `${nd}d20${mods}`;
if (reliableTalent) formula = `{${nd}d20${mods},10}kh`; if (reliableTalent) formula = `{${nd}d20${mods},10}kh`;
parts.unshift(formula); parts.unshift(formula);
// Optionally include a situational bonus // Optionally include a situational bonus
if ( form ) { if ( form ) {
data['bonus'] = form.bonus.value; data['bonus'] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value; messageOptions.rollMode = form.rollMode.value;
} }
if (!data["bonus"]) parts.pop(); if (!data["bonus"]) parts.pop();
// Optionally include an ability score selection (used for tool checks) // Optionally include an ability score selection (used for tool checks)
const ability = form ? form.ability : null; const ability = form ? form.ability : null;
if (ability && ability.value) { if (ability && ability.value) {
data.ability = ability.value; data.ability = ability.value;
const abl = data.abilities[data.ability]; const abl = data.abilities[data.ability];
if (abl) { if (abl) {
data.mod = abl.mod; data.mod = abl.mod;
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`; messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
}
} }
}
// Execute the roll // Execute the roll
let roll = new Roll(parts.join(" + "), data); let roll = new Roll(parts.join(" + "), data);
@ -139,73 +139,76 @@
*/ */
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) { async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) {
// Render modal dialog // Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html"; template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = { let dialogData = {
formula: parts.join(" + "), formula: parts.join(" + "),
data: data, data: data,
rollMode: rollMode, rollMode: rollMode,
rollModes: CONFIG.Dice.rollModes, rollModes: CONFIG.Dice.rollModes,
config: CONFIG.SW5E config: CONFIG.SW5E
}; };
const html = await renderTemplate(template, dialogData); const html = await renderTemplate(template, dialogData);
// Create the Dialog window // Create the Dialog window
return new Promise(resolve => { return new Promise(resolve => {
new Dialog({ new Dialog({
title: title, title: title,
content: html, content: html,
buttons: { buttons: {
advantage: { advantage: {
label: game.i18n.localize("SW5E.Advantage"), label: game.i18n.localize("SW5E.Advantage"),
callback: html => resolve(roll(parts, 1, html[0].querySelector("form"))) callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
},
normal: {
label: game.i18n.localize("SW5E.Normal"),
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
},
disadvantage: {
label: game.i18n.localize("SW5E.Disadvantage"),
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
}
}, },
default: "normal", normal: {
close: () => resolve(null) label: game.i18n.localize("SW5E.Normal"),
}, dialogOptions).render(true); callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
}); },
} disadvantage: {
label: game.i18n.localize("SW5E.Disadvantage"),
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
}
},
default: "normal",
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".
* This chooses the default options of a normal attack with no bonus, Critical, or no bonus respectively
*
* @param {Array} parts The dice roll component parts, excluding the initial d20
* @param {Actor} actor The Actor making the damage roll
* @param {Object} data Actor or item data against which to parse the roll
* @param {Event|object}[event The triggering event which initiated the roll
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
* @param {String} template The HTML template used to render the roll dialog
* @param {String} title The dice roll UI window title
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
* @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
* @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll
* @param {object} messageData Additional data which is applied to the created Chat Message, if any
*
* @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, criticalBonusDice=0, criticalMultiplier=2, fastForward=null,
dialogOptions={}, chatMessage=true, messageData={}}={}) {
/**
* A standardized helper function for managing core 5e "d20 rolls"
*
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
* This chooses the default options of a normal attack with no bonus, Critical, or no bonus respectively
*
* @param {Array} parts The dice roll component parts, excluding the initial d20
* @param {Actor} actor The Actor making the damage roll
* @param {Object} data Actor or item data against which to parse the roll
* @param {Event|object}[event The triggering event which initiated the roll
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
* @param {String} template The HTML template used to render the roll dialog
* @param {String} title The dice roll UI window title
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
* @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 {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
* @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll
* @param {object} messageData Additional data which is applied to the created Chat Message, if any
*
* @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={}}={}) {
// Prepare Message Data // Prepare Message Data
messageData.flavor = flavor || title; messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker(); messageData.speaker = speaker || ChatMessage.getSpeaker();
@ -213,8 +216,8 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
parts = parts.concat(["@bonus"]); parts = parts.concat(["@bonus"]);
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
// Define inner roll function // Define inner roll function
const _roll = function(parts, crit, form) { const _roll = function(parts, crit, form) {
// Optionally include a situational bonus // Optionally include a situational bonus
if ( form ) { if ( form ) {
@ -224,17 +227,17 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
if (!data["bonus"]) parts.pop(); if (!data["bonus"]) parts.pop();
// Create the damage roll // Create the damage roll
let roll = new Roll(parts.join("+"), data); let roll = new Roll(parts.join("+"), data);
// Modify the damage formula for critical hits // Modify the damage formula for critical hits
if ( crit === true ) { if ( crit === true ) {
let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0; roll.alter(criticalMultiplier, 0); // Multiply all dice
let mult = 2; if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term
// TODO Backwards compatibility - REMOVE LATER roll.terms[0].alter(1, criticalBonusDice);
if (isNewerVersion(game.data.version, "0.6.9")) roll.alter(mult, add); roll._formula = roll.formula;
else roll.alter(add, mult); }
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`; messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true; if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
} }
// Execute the roll // Execute the roll

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 // Power Level, School, and Components
if ( itemData.type === "power" ) { if ( itemData.type === "power" ) {
data.preparation.mode = data.preparation.mode || "prepared";
labels.level = C.powerLevels[data.level]; labels.level = C.powerLevels[data.level];
labels.school = C.powerSchools[data.school]; labels.school = C.powerSchools[data.school];
labels.components = Object.entries(data.components).reduce((arr, c) => { labels.components = Object.entries(data.components).reduce((arr, c) => {
@ -180,33 +181,33 @@ export default class Item5e extends Item {
else labels.featType = game.i18n.localize("SW5E.Passive"); else labels.featType = game.i18n.localize("SW5E.Passive");
} }
// Species Items // Species Items
else if ( itemData.type === "species" ) { else if ( itemData.type === "species" ) {
// labels.species = C.species[data.species]; // labels.species = C.species[data.species];
} }
// Archetype Items // Archetype Items
else if ( itemData.type === "archetype" ) { else if ( itemData.type === "archetype" ) {
// labels.archetype = C.archetype[data.archetype]; // labels.archetype = C.archetype[data.archetype];
} }
// Background Items // Background Items
else if ( itemData.type === "background" ) { else if ( itemData.type === "background" ) {
// labels.background = C.background[data.background];
} }
// Class Feature Items // Class Feature Items
else if ( itemData.type === "classfeature" ) { else if ( itemData.type === "classfeature" ) {
// labels.classFeature = C.classFeature[data.classFeature];
} }
// Fighting Style Items // Fighting Style Items
else if ( itemData.type === "fightingstyle" ) { else if ( itemData.type === "fightingstyle" ) {
// labels.fightingstyle = C.fightingstyle[data.fightingstyle];
} }
// Fighting Mastery Items // Fighting Mastery Items
else if ( itemData.type === "fightingmastery" ) { else if ( itemData.type === "fightingmastery" ) {
// labels.fightingmastery = C.fightingmastery[data.fightingmastery];
} }
// Lightsaber Form Items // Lightsaber Form Items
else if ( itemData.type === "lightsaberform" ) { else if ( itemData.type === "lightsaberform" ) {
// labels.lightsaberform = C.lightsaberform[data.lightsaberform];
} }
// Equipment Items // Equipment Items
@ -322,7 +323,7 @@ export default class Item5e extends Item {
user: game.user._id, user: game.user._id,
type: CONST.CHAT_MESSAGE_TYPES.OTHER, type: CONST.CHAT_MESSAGE_TYPES.OTHER,
content: html, content: html,
flavor: this.name, flavor: this.data.data.chatFlavor || this.name,
speaker: { speaker: {
actor: this.actor._id, actor: this.actor._id,
token: this.actor.token, token: this.actor.token,
@ -348,7 +349,7 @@ export default class Item5e extends Item {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* For items which consume a resource, handle the consumption of that resource when the item is used. * For items which consume a resource, handle the consumption of that resource when the item is used.
* There are four types of ability consumptions which are handled: * There are four types of ability consumptions which are handled:
* 1. Ammunition (on attack rolls) * 1. Ammunition (on attack rolls)
@ -367,7 +368,6 @@ export default class Item5e extends Item {
if ( !consume.type ) return true; if ( !consume.type ) return true;
const actor = this.actor; const actor = this.actor;
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type]; const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
const amount = parseInt(consume.amount || 1);
// Only handle certain types for certain actions // Only handle certain types for certain actions
if ( ((consume.type === "ammo") && !isAttack ) || ((consume.type !== "ammo") && !isCard) ) return true; 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 // Identify the consumed resource and it's quantity
let consumed = null; let consumed = null;
let amount = parseInt(consume.amount || 1);
let quantity = 0; let quantity = 0;
switch ( consume.type ) { switch ( consume.type ) {
case "attribute": case "attribute":
@ -393,7 +394,13 @@ export default class Item5e extends Item {
break; break;
case "charges": case "charges":
consumed = actor.items.get(consume.target); 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; break;
} }
@ -418,7 +425,11 @@ export default class Item5e extends Item {
await consumed.update({"data.quantity": remaining}); await consumed.update({"data.quantity": remaining});
break; break;
case "charges": 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; return true;
} }
@ -436,7 +447,7 @@ export default class Item5e extends Item {
// Configure whether to consume a limited use or to place a template // Configure whether to consume a limited use or to place a template
const charge = this.data.data.recharge; const charge = this.data.data.recharge;
const uses = this.data.data.uses; const uses = this.data.data.uses;
let usesCharges = !!uses.per && (uses.max > 0); let usesCharges = !!uses.per && !!uses.max;
let placeTemplate = false; let placeTemplate = false;
let consume = charge.value || usesCharges; let consume = charge.value || usesCharges;
@ -637,40 +648,37 @@ export default class Item5e extends Item {
} }
// Attack Bonus // Attack Bonus
if ( itemData.attackBonus ) parts.push(itemData.attackBonus);
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {}; const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
if ( itemData.attackBonus || actorBonus.attack ) { if ( actorBonus.attack ) parts.push(actorBonus.attack);
parts.push("@atk");
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
}
// Ammunition Bonus // Ammunition Bonus
delete this._ammo; delete this._ammo;
const consume = itemData.consume; const consume = itemData.consume;
if ( consume?.type === "ammo" ) { if ( consume?.type === "ammo" ) {
const ammo = this.actor.items.get(consume.target); const ammo = this.actor.items.get(consume.target);
if(ammo?.data){ if(ammo?.data){
const q = ammo.data.data.quantity; const q = ammo.data.data.quantity;
const consumeAmount = consume.amount ?? 0; const consumeAmount = consume.amount ?? 0;
if ( q && (q - consumeAmount >= 0) ) { if ( q && (q - consumeAmount >= 0) ) {
let ammoBonus = ammo.data.data.attackBonus; this._ammo = ammo;
if ( ammoBonus ) { let ammoBonus = ammo.data.data.attackBonus;
parts.push("@ammo"); if ( ammoBonus ) {
rollData["ammo"] = ammoBonus; parts.push("@ammo");
title += ` [${ammo.name}]`; rollData["ammo"] = ammoBonus;
this._ammo = ammo; title += ` [${ammo.name}]`;
}
} }
//}else{
// ui.notifications.error(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
} }
} }
}
// Compose roll options // Compose roll options
const rollConfig = mergeObject({ const rollConfig = mergeObject({
parts: parts, parts: parts,
actor: this.actor, actor: this.actor,
data: rollData, data: rollData,
title: title, title: title,
flavor: title,
speaker: ChatMessage.getSpeaker({actor: this.actor}), speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: { dialogOptions: {
width: 400, width: 400,
@ -681,9 +689,11 @@ export default class Item5e extends Item {
}, options); }, options);
rollConfig.event = options.event; rollConfig.event = options.event;
// Expanded weapon critical threshold // Expanded critical hit thresholds
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) { if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
rollConfig.critical = parseInt(flags.weaponCriticalThreshold); rollConfig.critical = parseInt(flags.weaponCriticalThreshold);
} else if (( this.data.type === "power" ) && flags.powerCriticalThreshold) {
rollConfig.critical = parseInt(flags.powerCriticalThreshold);
} }
// Elven Accuracy // Elven Accuracy
@ -710,28 +720,41 @@ export default class Item5e extends Item {
/** /**
* Place a damage roll using an item (weapon, feat, power, or equipment) * 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
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance * @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 itemData = this.data.data;
const actorData = this.actor.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 }}; const messageData = {"flags.sw5e.roll": {type: "damage", itemId: this.id }};
// Get roll data // Get roll data
const parts = itemData.damage.parts.map(d => d[0]);
const rollData = this.getRollData(); const rollData = this.getRollData();
if ( powerLevel ) rollData.item.level = powerLevel; if ( powerLevel ) rollData.item.level = powerLevel;
// Get message labels // Configure the damage roll
const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`; const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`;
let flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title; const rollConfig = {
event: event,
// Define Roll parts parts: parts,
const parts = itemData.damage.parts.map(d => d[0]); 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 // Adjust damage from versatile usage
if ( versatile && itemData.damage.versatile ) { 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}`) || {}; const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) { if ( actorBonus.damage && (parseInt(actorBonus.damage) !== 0) ) {
parts.push("@dmg"); parts.push(actorBonus.damage);
rollData["dmg"] = actorBonus.damage;
} }
// Ammunition Damage // Add ammunition damage
if ( this._ammo ) { if ( this._ammo ) {
parts.push("@ammo"); parts.push("@ammo");
rollData["ammo"] = this._ammo.data.data.damage.parts.map(p => p[0]).join("+"); 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; 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 // Call the roll helper utility
return damageRoll({ return damageRoll(mergeObject(rollConfig, options));
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
});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -793,22 +806,7 @@ export default class Item5e extends Item {
_scaleAtWillDamage(parts, scale, level, rollData) { _scaleAtWillDamage(parts, scale, level, rollData) {
const add = Math.floor((level + 1) / 6); const add = Math.floor((level + 1) / 6);
if ( add === 0 ) return; if ( add === 0 ) return;
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData);
// 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}`);
}
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -826,20 +824,7 @@ export default class Item5e extends Item {
_scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) { _scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) {
const upcastLevels = Math.max(powerLevel - baseLevel, 0); const upcastLevels = Math.max(powerLevel - baseLevel, 0);
if ( upcastLevels === 0 ) return parts; if ( upcastLevels === 0 ) return parts;
this._scaleDamage(parts, formula, upcastLevels, rollData);
// 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;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -882,7 +867,7 @@ export default class Item5e extends Item {
/** /**
* Place an attack roll using an item (weapon, feat, power, or equipment) * Place an attack roll using an item (weapon, feat, power, or equipment)
* Rely upon the d20Roll logic for the core implementation * Rely upon the d20Roll logic for the core implementation
* *
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance * @return {Promise<Roll>} A Promise which resolves to the created Roll instance
*/ */
async rollFormula(options={}) { async rollFormula(options={}) {
@ -899,7 +884,7 @@ export default class Item5e extends Item {
const roll = new Roll(rollData.item.formula, rollData).roll(); const roll = new Roll(rollData.item.formula, rollData).roll();
roll.toMessage({ roll.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}), speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: this.data.data.chatFlavor || title, flavor: title,
rollMode: game.settings.get("core", "rollMode"), rollMode: game.settings.get("core", "rollMode"),
messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }} messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }}
}); });
@ -910,7 +895,7 @@ export default class Item5e extends Item {
/** /**
* Use a consumable item, deducting from the quantity or charges of the item. * Use a consumable item, deducting from the quantity or charges of the item.
* @param {boolean} configureDialog Whether to show a configuration dialog * @param {boolean} configureDialog Whether to show a configuration dialog
* @return {boolean} Whether further execution should be prevented * @return {boolean} Whether further execution should be prevented
* @private * @private
*/ */
@ -972,7 +957,7 @@ export default class Item5e extends Item {
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize(); if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
} }
return true; return true;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -1021,7 +1006,7 @@ export default class Item5e extends Item {
template: "systems/sw5e/templates/chat/tool-roll-dialog.html", template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
title: title, title: title,
speaker: ChatMessage.getSpeaker({actor: this.actor}), speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`, flavor: title,
dialogOptions: { dialogOptions: {
width: 400, width: 400,
top: options.event ? options.event.clientY - 80 : null, top: options.event ? options.event.clientY - 80 : null,
@ -1055,8 +1040,8 @@ export default class Item5e extends Item {
} }
// Include a proficiency score // Include a proficiency score
const prof = "proficient" in rollData.item ? (rollData.item.proficient || 0) : 1; const prof = ("proficient" in rollData.item) ? (rollData.item.proficient || 0) : 1;
rollData["prof"] = Math.floor(prof * rollData.attributes.prof); rollData["prof"] = Math.floor(prof * (rollData.attributes.prof || 0));
return rollData; return rollData;
} }
@ -1189,7 +1174,7 @@ export default class Item5e extends Item {
if ( !targets.length ) ui.notifications.warn(game.i18n.localize("SW5E.ActionWarningNoToken")); if ( !targets.length ) ui.notifications.warn(game.i18n.localize("SW5E.ActionWarningNoToken"));
return targets; return targets;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Factory Methods */ /* Factory Methods */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -1,4 +1,5 @@
import TraitSelector from "../apps/trait-selector.js"; 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 * 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 { export default class ItemSheet5e extends ItemSheet {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
// Expand the default size of the class sheet
if ( this.object.data.type === "class" ) { 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() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
width: 560, width: 560,
height: "auto", height: 400,
classes: ["sw5e", "sheet", "item"], classes: ["sw5e", "sheet", "item"],
resizable: true, resizable: true,
scrollY: [".tab.details"], scrollY: [".tab.details"],
@ -37,17 +41,17 @@ export default class ItemSheet5e extends ItemSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
getData() { async getData(options) {
const data = super.getData(); const data = super.getData(options);
data.labels = this.item.labels; data.labels = this.item.labels;
data.config = CONFIG.SW5E; data.config = CONFIG.SW5E;
// Item Type, Status, and Details // 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.itemStatus = this._getItemStatus(data.item);
data.itemProperties = this._getItemProperties(data.item); data.itemProperties = this._getItemProperties(data.item);
data.isPhysical = data.item.data.hasOwnProperty("quantity"); data.isPhysical = data.item.data.hasOwnProperty("quantity");
// Potential consumption targets // Potential consumption targets
data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item); data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item);
@ -55,17 +59,20 @@ export default class ItemSheet5e extends ItemSheet {
data.hasAttackRoll = this.item.hasAttack; data.hasAttackRoll = this.item.hasAttack;
data.isHealing = data.item.data.actionType === "heal"; data.isHealing = data.item.data.actionType === "heal";
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat"; data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
data.isLine = ["line", "wall"].includes(data.item.data.target?.type); data.isLine = ["line", "wall"].includes(data.item.data.target?.type);
// Vehicles // Vehicles
data.isCrewed = data.item.data.activation?.type === 'crew'; data.isCrewed = data.item.data.activation?.type === 'crew';
data.isMountable = this._isItemMountable(data.item); data.isMountable = this._isItemMountable(data.item);
// Prepare Active Effects
data.effects = prepareActiveEffectCategories(this.entity.effects);
return data; return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Get the valid item consumption targets which exist on the actor * Get the valid item consumption targets which exist on the actor
* @param {Object} item Item data for the item being displayed * @param {Object} item Item data for the item being displayed
* @return {{string: string}} An object of potential consumption targets * @return {{string: string}} An object of potential consumption targets
@ -109,6 +116,8 @@ export default class ItemSheet5e extends ItemSheet {
// Charges // Charges
else if ( consume.type === "charges" ) { else if ( consume.type === "charges" ) {
return actor.items.reduce((obj, i) => { return actor.items.reduce((obj, i) => {
// Limited-use items
const uses = i.data.data.uses || {}; const uses = i.data.data.uses || {};
if ( uses.per && uses.max ) { if ( uses.per && uses.max ) {
const label = uses.per === "charges" ? 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})})`; ` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {max: uses.max, per: uses.per})})`;
obj[i.id] = i.name + label; 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; return obj;
}, {}) }, {})
} }
@ -177,31 +190,26 @@ export default class ItemSheet5e extends ItemSheet {
} }
else if ( item.type === "species" ) { else if ( item.type === "species" ) {
//props.push(labels.species);
} }
else if ( item.type === "archetype" ) { 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);
} }
else if ( item.type === "background" ) {
}
else if ( item.type === "classfeature" ) {
}
else if ( item.type === "fightingmastery" ) {
}
else if ( item.type === "fightingstyle" ) {
}
else if ( item.type === "lightsaberform" ) {
}
// Action type // Action type
if ( item.data.actionType ) { if ( item.data.actionType ) {
@ -240,8 +248,8 @@ export default class ItemSheet5e extends ItemSheet {
/** @override */ /** @override */
setPosition(position={}) { setPosition(position={}) {
if ( !this._minimized ) { if ( !(this._minimized || position.height) ) {
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height; position.height = (this._tabs[0].active === "details") ? "auto" : this.options.height;
} }
return super.setPosition(position); return super.setPosition(position);
} }
@ -251,17 +259,20 @@ export default class ItemSheet5e extends ItemSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
_updateObject(event, formData) { _getSubmitData(updateData={}) {
// TODO: This can be removed once 0.7.x is release channel // Create the expanded update data object
if ( !formData.data ) formData = expandObject(formData); 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 // Handle Damage array
const damage = formData.data?.damage; const damage = data.data?.damage;
if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]); if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// Update the Item // Return the flattened submission data
super._updateObject(event, formData); return flattenObject(data);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -269,8 +280,14 @@ export default class ItemSheet5e extends ItemSheet {
/** @override */ /** @override */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
html.find(".damage-control").click(this._onDamageControl.bind(this)); if ( this.isEditable ) {
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this)); 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 maximum: skills.number
}).render(true) }).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}); await a.update(updateData, {enforceTypes: false});
} }
} catch(err) { } catch(err) {
err.message = `Failed sw5e system migration for Actor ${a.name}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
@ -27,6 +28,7 @@ export const migrateWorld = async function() {
await i.update(updateData, {enforceTypes: false}); await i.update(updateData, {enforceTypes: false});
} }
} catch(err) { } catch(err) {
err.message = `Failed sw5e system migration for Item ${i.name}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
@ -40,15 +42,15 @@ export const migrateWorld = async function() {
await s.update(updateData, {enforceTypes: false}); await s.update(updateData, {enforceTypes: false});
} }
} catch(err) { } catch(err) {
err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
// Migrate World Compendium Packs // Migrate World Compendium Packs
const packs = game.packs.filter(p => { for ( let p of game.packs ) {
return (p.metadata.package === "world") && ["Actor", "Item", "Scene"].includes(p.metadata.entity) if ( p.metadata.package !== "world" ) continue;
}); if ( !["Actor", "Item", "Scene"].includes(p.metadata.entity) ) continue;
for ( let p of packs ) {
await migrateCompendium(p); await migrateCompendium(p);
} }
@ -68,27 +70,46 @@ export const migrateCompendium = async function(pack) {
const entity = pack.metadata.entity; const entity = pack.metadata.entity;
if ( !["Actor", "Item", "Scene"].includes(entity) ) return; if ( !["Actor", "Item", "Scene"].includes(entity) ) return;
// Unlock the pack for editing
const wasLocked = pack.locked;
await pack.configure({locked: false});
// Begin by requesting server-side data model migration and get the migrated content // Begin by requesting server-side data model migration and get the migrated content
await pack.migrate(); await pack.migrate();
const content = await pack.getContent(); const content = await pack.getContent();
// Iterate over compendium entries - applying fine-tuned migration functions // Iterate over compendium entries - applying fine-tuned migration functions
for ( let ent of content ) { for ( let ent of content ) {
let updateData = {};
try { try {
let updateData = null; switch (entity) {
if (entity === "Item") updateData = migrateItemData(ent.data); case "Actor":
else if (entity === "Actor") updateData = migrateActorData(ent.data); updateData = migrateActorData(ent.data);
else if ( entity === "Scene" ) updateData = migrateSceneData(ent.data); break;
if (!isObjectEmpty(updateData)) { case "Item":
expandObject(updateData); updateData = migrateItemData(ent.data);
updateData["_id"] = ent._id; break;
await pack.updateEntity(updateData); case "Scene":
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`); updateData = migrateSceneData(ent.data);
break;
} }
} catch(err) { 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}`);
}
// Handle migration failures
catch(err) {
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`;
console.error(err); 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}`); console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
}; };
@ -107,9 +128,7 @@ export const migrateActorData = function(actor) {
// Actor Data Updates // Actor Data Updates
_migrateActorBonuses(actor, updateData); _migrateActorBonuses(actor, updateData);
_migrateActorMovement(actor, updateData);
// Remove deprecated fields
_migrateRemoveDeprecated(actor, updateData);
// Migrate Owned Items // Migrate Owned Items
if ( !actor.items ) return updateData; if ( !actor.items ) return updateData;
@ -172,11 +191,6 @@ function cleanActorData(actorData) {
*/ */
export const migrateItemData = function(item) { export const migrateItemData = function(item) {
const updateData = {}; const updateData = {};
// Remove deprecated fields
_migrateRemoveDeprecated(item, updateData);
// Return the migrated update data
return updateData; 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 * @private
*/ */
const _migrateRemoveDeprecated = function(ent, updateData) { function _migrateActorMovement(actor, updateData) {
const flat = flattenObject(ent.data); if ( actor.data.attributes?.movement?.walk !== undefined ) return;
const s = (actor.data.attributes?.speed?.value || "").split(" ");
// Identify objects to deprecate if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
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;
}
};
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -280,3 +280,24 @@ export async function purgeFlags(pack) {
} }
await pack.configure({locked: true}); 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

@ -37,7 +37,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
templateData.width = target.value; templateData.width = target.value;
templateData.direction = 45; templateData.direction = 45;
break; break;
case "ray": // 5e rays are most commonly 1 square (5 ft) in width case "ray": // 5e rays are most commonly 1 square (5 ft) in width
templateData.width = target.width ?? canvas.dimensions.distance; templateData.width = target.width ?? canvas.dimensions.distance;
break; break;
default: default:

View file

@ -7,11 +7,11 @@ export const registerSystemSettings = function() {
name: "System Migration Version", name: "System Migration Version",
scope: "world", scope: "world",
config: false, config: false,
type: Number, type: String,
default: 0 default: ""
}); });
/** /**
* Register resting variants * Register resting variants
*/ */
game.settings.register("sw5e", "restVariant", { game.settings.register("sw5e", "restVariant", {
@ -82,18 +82,6 @@ export const registerSystemSettings = function() {
type: Boolean, 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 * Option to automatically collapse Item Card descriptions
*/ */

View file

@ -4,17 +4,16 @@
* @return {Promise} * @return {Promise}
*/ */
export const preloadHandlebarsTemplates = async function() { export const preloadHandlebarsTemplates = async function() {
return loadTemplates([
// Define template paths to load // Shared Partials
const templatePaths = [ "systems/sw5e/templates/actors/parts/active-effects.html",
// Actor Sheet Partials // Actor Sheet Partials
"systems/sw5e/templates/actors/oldActor/parts/actor-traits.html", "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-inventory.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-features.html", "systems/sw5e/templates/actors/oldActor/parts/actor-features.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html", "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-effects.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.html", "systems/sw5e/templates/actors/newActor/parts/swalt-biography.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-core.html", "systems/sw5e/templates/actors/newActor/parts/swalt-core.html",
@ -30,8 +29,5 @@ export const preloadHandlebarsTemplates = async function() {
"systems/sw5e/templates/items/parts/item-activation.html", "systems/sw5e/templates/items/parts/item-activation.html",
"systems/sw5e/templates/items/parts/item-description.html", "systems/sw5e/templates/items/parts/item-description.html",
"systems/sw5e/templates/items/parts/item-mountable.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 */ /* Trait Lists */
/* ----------------------------------------- */ /* ----------------------------------------- */
/* ----------------------------------------- */
/* Items Lists */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* Active Effects */
/* ----------------------------------------- */
} }
.sw5e.sheet .window-content { .sw5e.sheet .window-content {
overflow-y: hidden; overflow-y: hidden;
@ -404,6 +410,88 @@
margin: 0; margin: 0;
padding: 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 /* Trait Selector
/* ----------------------------------------- */ /* ----------------------------------------- */
@ -447,9 +535,6 @@
/* Powerbook */ /* Powerbook */
/* ----------------------------------------- */ /* ----------------------------------------- */
/* ----------------------------------------- */ /* ----------------------------------------- */
/* Active Effects */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* TinyMCE */ /* TinyMCE */
/* ----------------------------------------- */ /* ----------------------------------------- */
} }
@ -515,6 +600,7 @@
font-weight: 400; font-weight: 400;
color: #4b4a44; color: #4b4a44;
border-bottom: 1px solid #c9c7b8; border-bottom: 1px solid #c9c7b8;
white-space: nowrap;
} }
.sw5e.sheet.actor .tab.attributes { .sw5e.sheet.actor .tab.attributes {
overflow: hidden; overflow: hidden;
@ -559,6 +645,7 @@
font-family: "Signika", sans-serif; font-family: "Signika", sans-serif;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
white-space: nowrap;
} }
.sw5e.sheet.actor .ability-scores { .sw5e.sheet.actor .ability-scores {
flex: 0 0 100px; flex: 0 0 100px;
@ -771,35 +858,10 @@
border-bottom: 2px groove #eeede0; border-bottom: 2px groove #eeede0;
} }
.sw5e.sheet.actor .inventory-list { .sw5e.sheet.actor .inventory-list {
list-style: none;
margin: 0;
padding: 0 5px; 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 { .sw5e.sheet.actor .inventory-list .item .item-name {
cursor: pointer; 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 { .sw5e.sheet.actor .inventory-list .item .item-name.rollable:hover .item-image {
background-image: url("../../icons/svg/d20-grey.svg") !important; background-image: url("../../icons/svg/d20-grey.svg") !important;
@ -821,36 +883,14 @@
flex: 0 0 80px; flex: 0 0 80px;
text-align: right; text-align: right;
font-size: 11px; font-size: 11px;
color: #7a7971;
white-space: nowrap; 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 { .sw5e.sheet.actor .inventory-list .inventory-header .item-controls a.item-create {
flex: 0 0 100%; flex: 0 0 100%;
} }
.sw5e.sheet.actor .inventory-list .item-name {
color: #191813;
}
.sw5e.sheet.actor .inventory-list .item-detail { .sw5e.sheet.actor .inventory-list .item-detail {
flex: 0 0 70px; flex: 0 0 70px;
font-size: 12px; font-size: 12px;
color: #7a7971;
text-align: center; text-align: center;
border-right: 1px solid #c9c7b8; border-right: 1px solid #c9c7b8;
word-break: break-word; word-break: break-word;
@ -868,45 +908,15 @@
border-left: 1px solid #c9c7b8; border-left: 1px solid #c9c7b8;
border-right: 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 { .sw5e.sheet.actor .inventory-list .item-controls {
flex: 0 0 44px; 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 { .sw5e.sheet.actor .inventory-list .item-summary {
flex: 0 0 100%; flex: 0 0 100%;
font-size: 12px; font-size: 12px;
line-height: 16px; line-height: 16px;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
color: #191813;
border-top: 1px solid #c9c7b8; border-top: 1px solid #c9c7b8;
} }
.sw5e.sheet.actor .encumbrance { .sw5e.sheet.actor .encumbrance {
@ -1034,42 +1044,25 @@
.sw5e.sheet.actor .powerbook-empty .item-controls { .sw5e.sheet.actor .powerbook-empty .item-controls {
flex: 1; 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 { .sw5e.sheet.actor .editor {
padding: 0 8px; 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 { .sw5e.sheet.item {
min-height: 420px; min-height: 400px;
max-height: 95%;
min-width: 480px;
/* ----------------------------------------- */ /* ----------------------------------------- */
/* Sheet Header */ /* Sheet Header */
/* ----------------------------------------- */ /* ----------------------------------------- */
@ -1090,7 +1083,7 @@
border: 2px solid #000; border: 2px solid #000;
} }
.sw5e.sheet.item .sheet-header .item-subtitle { .sw5e.sheet.item .sheet-header .item-subtitle {
flex: 0 0 80px; flex: 0 0 100px;
height: 60px; height: 60px;
margin: 0; margin: 0;
padding: 5px; padding: 5px;
@ -1165,14 +1158,15 @@
.sw5e.sheet.item .details .form-group.input-select-select select { .sw5e.sheet.item .details .form-group.input-select-select select {
flex: 1.5; 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 { .sw5e.sheet.item .details .form-group.uses-per input {
flex: 1; flex: 0 0 32px;
} }
.sw5e.sheet.item .details .form-group.uses-per span { .sw5e.sheet.item .details .form-group.uses-per span {
flex: 0 0 16px; flex: 0 0 16px;
} margin: 0 4px 0 0;
.sw5e.sheet.item .details .form-group.uses-per select {
flex: 3;
} }
.sw5e.sheet.item .details span.sep { .sw5e.sheet.item .details span.sep {
flex: 0 0 8px; flex: 0 0 8px;
@ -1544,30 +1538,3 @@
max-width: 40px; max-width: 40px;
text-align: right; 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 ItemSheet5e from "./module/item/sheet.js";
import ShortRestDialog from "./module/apps/short-rest.js"; import ShortRestDialog from "./module/apps/short-rest.js";
import TraitSelector from "./module/apps/trait-selector.js"; import TraitSelector from "./module/apps/trait-selector.js";
import MovementConfig from "./module/apps/movement-config.js";
// Import Helpers // Import Helpers
import * as chat from "./module/chat.js"; import * as chat from "./module/chat.js";
@ -56,7 +57,8 @@ Hooks.once("init", function() {
ActorSheet5eVehicle, ActorSheet5eVehicle,
ItemSheet5e, ItemSheet5e,
ShortRestDialog, ShortRestDialog,
TraitSelector TraitSelector,
MovementConfig
}, },
canvas: { canvas: {
AbilityTemplate AbilityTemplate
@ -76,7 +78,7 @@ Hooks.once("init", function() {
CONFIG.SW5E = SW5E; CONFIG.SW5E = SW5E;
CONFIG.Actor.entityClass = Actor5e; CONFIG.Actor.entityClass = Actor5e;
CONFIG.Item.entityClass = Item5e; 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 // Add DND5e namespace for module compatability
game.dnd5e = game.sw5e; game.dnd5e = game.sw5e;
@ -139,18 +141,18 @@ Hooks.once("setup", function() {
// Localize CONFIG objects once up-front // Localize CONFIG objects once up-front
const toLocalize = [ const toLocalize = [
"abilities", "abilityAbbreviations", "alignments", "conditionTypes", "consumableTypes", "currencies", "abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments",
"damageTypes", "damageResistanceTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes",
"limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes", "powerSchools", "damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages",
"powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages", "limitedUsePeriods", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills",
"polymorphSettings", "armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes", "powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes",
"abilityConsumptionTypes", "actorSizes", "proficiencyLevels", "armorPropertiesTypes", "cover" "timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes"
]; ];
// Exclude some from sorting where the default order matters // Exclude some from sorting where the default order matters
const noSort = [ const noSort = [
"abilities", "alignments", "currencies", "distanceUnits", "itemActionTypes", "proficiencyLevels", "abilities", "alignments", "currencies", "distanceUnits", "movementUnits", "itemActionTypes", "proficiencyLevels",
"limitedUsePeriods", "powerComponents", "powerLevels", "weaponTypes" "limitedUsePeriods", "powerComponents", "powerLevels", "powerPreparationModes", "weaponTypes"
]; ];
// Localize and sort CONFIG objects // Localize and sort CONFIG objects
@ -178,22 +180,23 @@ Hooks.once("setup", function() {
*/ */
Hooks.once("ready", 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 // Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot)); Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot));
// Determine whether a system migration is required and feasible
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", "name": "sw5e",
"title": "SW 5th Edition", "title": "SW 5th Edition",
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.", "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", "author": "Dev Team",
"scripts": [], "scripts": [],
"esmodules": ["sw5e.js"], "esmodules": ["sw5e.js"],
@ -56,17 +56,17 @@
"path": "./packs/packs/feats.db", "path": "./packs/packs/feats.db",
"entity": "Item" "entity": "Item"
}, },
{ {
"name": "fightingstyles", "name": "fightingstyles",
"label": "Fighting Styles", "label": "Fighting Styles",
"path": "./packs/packs/fightingstyles.db", "path": "./packs/packs/fightingstyles.db",
"entity": "Item" "entity": "Item"
}, },
{ {
"name": "fightingmasteries", "name": "fightingmasteries",
"label": "Fighting Masteries", "label": "Fighting Masteries",
"path": "./packs/packs/fightingmasteries.db", "path": "./packs/packs/fightingmasteries.db",
"entity": "Item" "entity": "Item"
}, },
{ {
"name": "forcepowers", "name": "forcepowers",
@ -103,20 +103,20 @@
"label": "Species Traits", "label": "Species Traits",
"path": "./packs/packs/speciestraits.db", "path": "./packs/packs/speciestraits.db",
"entity": "Item" "entity": "Item"
}, },
{ {
"name": "tables", "name": "tables",
"label": "Tables", "label": "Tables",
"path": "./packs/packs/tables.db", "path": "./packs/packs/tables.db",
"entity": "RollTable" "entity": "RollTable"
}, },
{ {
"name": "techpowers", "name": "techpowers",
"label": "Tech Powers", "label": "Tech Powers",
"path": "./packs/packs/techpowers.db", "path": "./packs/packs/techpowers.db",
"entity": "Item" "entity": "Item"
}, },
{ {
"name": "weapons", "name": "weapons",
"label": "Weapons", "label": "Weapons",
"path": "./packs/packs/weapons.db", "path": "./packs/packs/weapons.db",
@ -135,8 +135,8 @@
"gridUnits": "ft", "gridUnits": "ft",
"primaryTokenAttribute": "attributes.hp", "primaryTokenAttribute": "attributes.hp",
"secondaryTokenAttribute": null, "secondaryTokenAttribute": null,
"minimumCoreVersion": "0.5.6", "minimumCoreVersion": "0.7.6",
"compatibleCoreVersion": "0.7.5", "compatibleCoreVersion": "0.7.6",
"url": "https://github.com/unrealkakeman89/sw5e", "url": "https://github.com/unrealkakeman89/sw5e",
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json", "manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip" "download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"

View file

@ -76,10 +76,18 @@
}, },
"creature": { "creature": {
"attributes": { "attributes": {
"movement": {
"burrow": 0,
"climb": 0,
"fly": 0,
"swim": 0,
"walk": 30,
"units": "ft",
"hover": false
},
"powercasting": "int", "powercasting": "int",
"speed": { "speed": {
"value": "30 ft", "_deprecated": true
"special": ""
} }
}, },
"details": { "details": {
@ -481,14 +489,14 @@
"value": "" "value": ""
} }
}, },
"speciesDescription": { "speciesDescription": {
"data": "$characteristics-table", "data": "$characteristics-table",
"description": { "description": {
"value": "", "value": "",
"chat": "", "chat": "",
"unidentified": "" "unidentified": ""
}, },
"traits": "" "traits": ""
}, },
"physicalItem": { "physicalItem": {
"quantity": 1, "quantity": 1,
@ -560,14 +568,14 @@
} }
} }
}, },
"archetype": { "archetype": {
"templates": ["archetypeDescription"], "templates": ["archetypeDescription"],
"className": "", "className": "",
"description": "" "description": ""
}, },
"background": { "background": {
"templates": ["backgroundDescription"] "templates": ["backgroundDescription"]
}, },
"backpack": { "backpack": {
"templates": ["itemDescription", "physicalItem"], "templates": ["itemDescription", "physicalItem"],
"capacity": { "capacity": {
@ -593,19 +601,19 @@
}, },
"powercasting": "none" "powercasting": "none"
}, },
"classfeature": { "classfeature": {
"templates": ["itemDescription", "activatedEffect", "action"], "templates": ["itemDescription", "activatedEffect", "action"],
"className": "" "className": ""
}, },
"fightingmastery": { "fightingmastery": {
"templates": ["fightingmasteryDescription"] "templates": ["fightingmasteryDescription"]
}, },
"fightingstyle": { "fightingstyle": {
"templates": ["fightingstyleDescription"] "templates": ["fightingstyleDescription"]
}, },
"lightsaberform": { "lightsaberform": {
"templates": ["lightsaberformDescription"] "templates": ["lightsaberformDescription"]
}, },
"consumable": { "consumable": {
"templates": ["itemDescription", "physicalItem", "activatedEffect", "action"], "templates": ["itemDescription", "physicalItem", "activatedEffect", "action"],
"consumableType": "potion", "consumableType": "potion",
@ -641,7 +649,7 @@
}, },
"species": { "species": {
"templates": ["speciesDescription"] "templates": ["speciesDescription"]
}, },
"tool": { "tool": {
"templates": ["itemDescription", "physicalItem"], "templates": ["itemDescription", "physicalItem"],
"ability": "int", "ability": "int",

View file

@ -82,25 +82,25 @@
</li> </li>
<li class="attribute"> <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"> <div class="attribute-value">
<input name="data.attributes.speed.value" type="text" <span>{{movement.primary}}</span>
value="{{data.attributes.speed.value}}" placeholder="0"/>
</div> </div>
<footer class="attribute-footer"> <footer class="attribute-footer">
<input type="text" class="speed" name="data.attributes.speed.special" <span>{{movement.special}}</span>
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
</footer> </footer>
</li> </li>
<li class="attribute initiative"> <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"> <div class="attribute-value">
<span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span> <span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
</div> </div>
<footer class="attribute-footer"> <footer class="attribute-footer">
<span>{{ localize "SW5E.Modifier" }}</span> <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}}"/> value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
</footer> </footer>
</li> </li>
@ -108,17 +108,17 @@
</section> </section>
</header> </header>
{{!-- NPC Sheet Navigation --}} {{!-- Character Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</a> <a class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</a>
<a class="item" data-tab="inventory">{{ localize "SW5E.Inventory" }}</a> <a class="item" data-tab="inventory">{{ localize "SW5E.Inventory" }}</a>
<a class="item" data-tab="features">{{ localize "SW5E.Features" }}</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="powerbook">{{ localize "SW5E.Powerbook" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a> <a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<a class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</a> <a class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</a>
</nav> </nav>
{{!-- NPC Sheet Body --}} {{!-- Character Sheet Body --}}
<section class="sheet-body"> <section class="sheet-body">
<div class="tab attributes flexrow" data-group="primary" data-tab="attributes"> <div class="tab attributes flexrow" data-group="primary" data-tab="attributes">
@ -183,7 +183,7 @@
{{!-- Counters --}} {{!-- Counters --}}
<div class="counters"> <div class="counters">
<div class="counter flexrow death-saves"> <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"> <div class="counter-value">
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
<input name="data.attributes.death.success" type="number" placeholder="0" <input name="data.attributes.death.success" type="number" placeholder="0"
@ -228,12 +228,12 @@
<div class="tab powerbook flexcol" data-group="primary" data-tab="powerbook"> <div class="tab powerbook flexcol" data-group="primary" data-tab="powerbook">
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}} {{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}}
</div> </div>
{{!-- Effects Tab --}} {{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects"> <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> </div>
{{!-- Biography Tab --}} {{!-- Biography Tab --}}
<div class="tab flexrow" data-group="primary" data-tab="biography"> <div class="tab flexrow" data-group="primary" data-tab="biography">
<div class="characteristics flexcol"> <div class="characteristics flexcol">

View file

@ -62,14 +62,14 @@
</li> </li>
<li class="attribute"> <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"> <div class="attribute-value">
<input name="data.attributes.speed.value" type="text" <span>{{movement.primary}}</span>
value="{{data.attributes.speed.value}}" placeholder="0"/>
</div> </div>
<footer class="attribute-footer"> <footer class="attribute-footer">
<input type="text" class="speed" name="data.attributes.speed.special" <span>{{movement.special}}</span>
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
</footer> </footer>
</li> </li>
</ul> </ul>
@ -81,6 +81,7 @@
<a class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</a> <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="features">{{ localize "SW5E.Features" }}</a>
<a class="item" data-tab="powerbook">{{ localize "SW5E.Powerbook" }}</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> <a class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</a>
</nav> </nav>
@ -143,8 +144,8 @@
<div class="counter-value"> <div class="counter-value">
<input name="data.resources.lair.value" type="checkbox" value="{{data.resources.lair.value}}" <input name="data.resources.lair.value" type="checkbox" value="{{data.resources.lair.value}}"
data-dtype="Boolean" {{checked data.resources.lair.value}}/> data-dtype="Boolean" {{checked data.resources.lair.value}}/>
<input name="data.resources.lair.initiative" type="number" value="{{data.resources.lair.initiative}}" placeholder="20"/> <input name="data.resources.lair.initiative" type="number" value="{{data.resources.lair.initiative}}" placeholder="20"/>
</div> </div>
</div> </div>
</div> </div>
@ -163,9 +164,14 @@
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}} {{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}}
</div> </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 --}} {{!-- Biography Tab --}}
<div class="tab biography flexcol" data-group="primary" data-tab="biography"> <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}} {{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
</div> </div>
</section> </section>
</form> </form>

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> </div>
{{/unless}} {{/unless}}
<ol class="inventory-list"> <ol class="items-list inventory-list">
{{#each sections as |section sid|}} {{#each sections as |section sid|}}
<li class="inventory-header flexrow"> <li class="items-header flexrow">
<h3 class="item-name flexrow">{{localize section.label}}</h3> <h3 class="item-name flexrow">{{localize section.label}}</h3>
{{#if section.hasActions}} {{#if section.hasActions}}
@ -25,7 +25,7 @@
{{/if}} {{/if}}
{{#if ../owner}} {{#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}}> <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"}} <i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
</a> </a>
@ -78,7 +78,7 @@
<div class="item-detail player-classfeatures"> <div class="item-detail player-classfeatures">
{{item.data.name}} {{item.data.name}}
</div> </div>
{{else if section.isFightingstyles}} {{else if section.isFightingstyles}}
<div class="item-detail player-fightingstyles"> <div class="item-detail player-fightingstyles">
{{item.data.name}} {{item.data.name}}
@ -119,7 +119,7 @@
{{/if}} {{/if}}
{{#if ../../owner}} {{#if ../../owner}}
<div class="item-controls"> <div class="item-controls flexrow">
{{#if section.crewable}} {{#if section.crewable}}
<a class="item-control item-toggle {{item.toggleClass}}" <a class="item-control item-toggle {{item.toggleClass}}"
title="{{item.toggleTitle}}"> title="{{item.toggleTitle}}">

View file

@ -4,7 +4,6 @@
<ol class="currency flexrow"> <ol class="currency flexrow">
<h3> <h3>
{{localize "SW5E.Currency"}} {{localize "SW5E.Currency"}}
<a class="currency-convert" title="Convert Currency"><i class="fas fa-coins"></i></a>
</h3> </h3>
{{#each data.currency as |v k|}} {{#each data.currency as |v k|}}
<label class="denomination {{k}}">{{ lookup ../config.currencies k }}</label> <label class="denomination {{k}}">{{ lookup ../config.currencies k }}</label>
@ -23,9 +22,9 @@
{{/unless}} {{/unless}}
</div> </div>
<ol class="inventory-list"> <ol class="items-list inventory-list">
{{#each sections as |section sid|}} {{#each sections as |section sid|}}
<li class="inventory-header flexrow"> <li class="items-header flexrow">
<h3 class="item-name flexrow">{{localize section.label}}</h3> <h3 class="item-name flexrow">{{localize section.label}}</h3>
{{#if section.columns}} {{#if section.columns}}
@ -33,18 +32,18 @@
<div class="item-detail {{css}}">{{label}}</div> <div class="item-detail {{css}}">{{label}}</div>
{{/each}} {{/each}}
{{else}} {{else}}
{{#if ../isCharacter}} {{#if ../isCharacter}}
<div class="item-detail item-weight">{{localize "SW5E.Weight"}}</div> <div class="item-detail item-weight">{{localize "SW5E.Weight"}}</div>
{{/if}} {{/if}}
<div class="item-detail item-uses">{{localize "SW5E.Charges"}}</div> <div class="item-detail item-uses">{{localize "SW5E.Charges"}}</div>
<div class="item-detail item-action">{{localize "SW5E.Usage"}}</div> <div class="item-detail item-action">{{localize "SW5E.Usage"}}</div>
{{/if}} {{/if}}
{{#if ../owner}} {{#if ../owner}}
<div class="item-controls"> <div class="item-controls flexrow">
<a class="item-control item-create" title='{{localize "SW5E.ItemCreate"}}' <a class="item-control item-create" title='{{localize "SW5E.ItemCreate"}}'
{{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}> {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}} <i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
</a> </a>
</div> </div>
@ -53,13 +52,13 @@
<ol class="item-list"> <ol class="item-list">
{{#each section.items as |item iid|}} {{#each section.items as |item iid|}}
<li class="item flexrow {{section.css}}" <li class="item flexrow {{section.css}}"
data-item-id="{{#if section.editableName}}{{iid}}{{else}}{{item._id}}{{/if}}"> data-item-id="{{#if section.editableName}}{{iid}}{{else}}{{item._id}}{{/if}}">
<div class="item-name flexrow rollable"> <div class="item-name flexrow rollable">
{{#if section.editableName}} {{#if section.editableName}}
<input type="text" value="{{item.name}}"> <input type="text" value="{{item.name}}">
{{else}} {{else}}
<div class="item-image" style="background-image: url('{{item.img}}')"></div> <div class="item-image" style="background-image: url('{{item.img}}')"></div>
<h4> <h4>
{{item.name~}} {{item.name~}}
{{~#if item.isStack}} ({{item.data.quantity}}){{/if}} {{~#if item.isStack}} ({{item.data.quantity}}){{/if}}
@ -82,32 +81,32 @@
</div> </div>
{{/each}} {{/each}}
{{else}} {{else}}
{{#if ../../isCharacter}} {{#if ../../isCharacter}}
<div class="item-detail item-weight"> <div class="item-detail item-weight">
{{#if item.totalWeight}} {{#if item.totalWeight}}
<div class="item-detail"> <div class="item-detail">
{{ item.totalWeight }} {{localize "SW5E.AbbreviationLbs"}} {{ item.totalWeight }} {{localize "SW5E.AbbreviationLbs"}}
</div>
{{/if}}
</div>
{{/if}}
<div class="item-detail item-uses">
{{#if item.hasUses }}
<input type="text" value="{{item.data.uses.value}}" placeholder="0"/>
/ {{item.data.uses.max}}
{{/if}}
</div> </div>
{{/if}}
</div>
{{/if}}
<div class="item-detail item-uses"> <div class="item-detail item-action">
{{#if item.hasUses }} {{#if item.data.activation.type }}
<input type="text" value="{{item.data.uses.value}}" placeholder="0"/> {{item.labels.activation}}
/ {{item.data.uses.max}} {{/if}}
{{/if}} </div>
</div>
<div class="item-detail item-action">
{{#if item.data.activation.type }}
{{item.labels.activation}}
{{/if}}
</div>
{{/if}} {{/if}}
{{#if ../../owner}} {{#if ../../owner}}
<div class="item-controls"> <div class="item-controls flexrow">
{{#unless @root.isVehicle}} {{#unless @root.isVehicle}}
<a class="item-control item-toggle {{item.toggleClass}}" title='{{item.toggleTitle}}'><i class="fas fa-shield-alt"></i></a> <a class="item-control item-toggle {{item.toggleClass}}" title='{{item.toggleTitle}}'><i class="fas fa-shield-alt"></i></a>
{{/unless}} {{/unless}}

View file

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

View file

@ -11,6 +11,11 @@
</div> </div>
{{#unless isVehicle}} {{#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}}"> <div class="form-group {{#unless data.traits.senses}}inactive{{/unless}}">
<label>{{localize "SW5E.Senses"}}</label> <label>{{localize "SW5E.Senses"}}</label>
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/> <input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/>

View file

@ -58,8 +58,7 @@
<li class="attribute"> <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"> <div class="attribute-value">
<input name="data.attributes.speed.value" type="text" placeholder="&mdash;" <input name="data.attributes.speed" type="text" placeholder="&mdash;" value="{{data.attributes.speed}}"/>
value="{{data.attributes.speed.value}}">
</div> </div>
</li> </li>
</ul> </ul>
@ -139,16 +138,16 @@
</div> </div>
</div> </div>
</div> </div>
{{> 'systems/sw5e/templates/actors/parts/actor-traits.html'}} {{> 'systems/sw5e/templates/actors/oldActor/parts/actor-traits.html'}}
</section> </section>
</div> </div>
<div class="tab features flexcol" data-group="primary" data-tab="features"> <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>
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo"> <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>
<div class="tab biography flexcol" data-group="primary" data-tab="biography"> <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,44 +1,48 @@
<form class="{{cssClass}}" autocomplete="off"> <form class="{{cssClass}}" autocomplete="off">
<p class="notes">{{localize 'SW5E.FlagsInstructions'}}</p> <section class="form-body">
<p class="notes">{{localize 'SW5E.FlagsInstructions'}}</p>
{{#each flags as |fs section|}} {{#each flags as |fs section|}}
<h3 class="form-header">{{localize section}}</h3> <h3 class="form-header">{{localize section}}</h3>
{{#each fs as |flag key|}} {{#each fs as |flag key|}}
<div class="form-group"> <div class="form-group">
<label>{{localize flag.name}}</label> <label>{{localize flag.name}}</label>
{{#if flag.isCheckbox}} {{#if flag.isCheckbox}}
<input type="checkbox" name="{{key}}" data-dtype="Boolean" {{checked flag.value}}/> <input type="checkbox" name="{{key}}" data-dtype="Boolean" {{checked flag.value}}/>
{{else if flag.isSelect}} {{else if flag.isSelect}}
<select name="{{key}}" data-dtype="{{flag.type}}"> <select name="{{key}}" data-dtype="{{flag.type}}">
{{#select flag.value}} {{#select flag.value}}
{{#each flag.choices as |v k|}} {{#each flag.choices as |v k|}}
<option value="{{k}}">{{localize v}}</option> <option value="{{k}}">{{localize v}}</option>
{{/each}} {{/each}}
{{/select}} {{/select}}
</select> </select>
{{else}} {{else}}
<input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/> <input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/>
{{/if}} {{/if}}
<p class="notes">{{localize flag.hint}}</p> <p class="notes">{{localize flag.hint}}</p>
</div> </div>
{{/each}} {{/each}}
{{/each}} {{/each}}
<h3 class="form-header">{{localize "SW5E.Bonuses"}}</h3> <h3 class="form-header">{{localize "SW5E.Bonuses"}}</h3>
<p class="notes">{{localize "SW5E.BonusesHint"}}</p> <p class="notes">{{localize "SW5E.BonusesHint"}}</p>
{{#each bonuses as |b|}} {{#each bonuses as |b|}}
<div class="form-group"> <div class="form-group">
<label>{{localize b.label}}</label> <label>{{localize b.label}}</label>
<input type="text" name="{{b.name}}" value="{{b.value}}"/> <input type="text" name="{{b.name}}" value="{{b.value}}"/>
</div> </div>
{{/each}} {{/each}}
</section>
<button type="submit" name="submit"> <footer class="form-footer">
<i class="far fa-save"></i> {{localize 'SW5E.FlagsSave'}} <button type="submit" name="submit">
</button> <i class="far fa-save"></i> {{localize 'SW5E.FlagsSave'}}
</button>
</footer>
</form> </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"/> <input type="text" name="custom" value="{{custom}}" data-dtype="String"/>
</div> </div>
{{/if}} {{/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> </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}}> <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"> <header class="card-header flexrow">
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/> <img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/>
<h3>{{item.name}}</h3> <h3 class="item-name">{{item.name}}</h3>
</header> </header>
<div class="card-content">{{{data.description.value}}}</div> <div class="card-content">{{{data.description.value}}}</div>

View file

@ -31,6 +31,7 @@
{{!-- Item Sheet Navigation --}} {{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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> --> <!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav> </nav>
@ -39,7 +40,13 @@
{{!-- Description Tab --}} {{!-- Description Tab --}}
<div class="tab description" data-group="primary" data-tab="description"> <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}} {{editor content=data.description.value target="data.description.value" button=true owner=owner editable=editable}}
</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> </section>
</form> </form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}} {{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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> --> <!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav> </nav>
@ -32,68 +33,42 @@
{{!-- Description Tab --}} {{!-- Description Tab --}}
<div class="tab description" data-group="primary" data-tab="description"> <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">{{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>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>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>Languages:</strong> {{{data.languages.value}}}</p></div>
<div class="background"><p><strong>Equipment:</strong> {{{data.equipment.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"><h3>{{{data.flavorName.value}}}</h3></div>
<div class="background"><p>{{{data.flavorDescription.value}}}</p></div> <div class="background"><p>{{{data.flavorDescription.value}}}</p></div>
<div class="smalltable"><p>{{{data.flavorOptions.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"><h2>Feature: {{{data.featureName.value}}}</h2></div>
<div class="background"><p>{{{data.featureText.value}}}</p></div> <div class="background"><p>{{{data.featureText.value}}}</p></div>
<h2>Background Feat</h2> <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> <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> <div class="smalltable"><p>{{{data.featOptions.value}}}</p></div>
<h3>Suggested Characteristics</h3> <h3>Suggested Characteristics</h3>
<div class="background"><p>{{{data.suggestedCharacteristics.value}}}</p></div> <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.personalityTraitOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.idealOptions.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.flawOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.bondOptions.value}}}</p></div> <div class="medtable"><p>{{{data.bondOptions.value}}}</p></div>
<script> <script>
let nullField = document.querySelectorAll('.background > div'); let nullField = document.querySelectorAll('.background > div');
nullField.forEach(function(element) { nullField.forEach(function(element) {
if (element.value === null) {
if (element.value === null) { element.previousElementSibling.style.display = 'none';
element.previousElementSibling.style.display = 'none'; element.style.display = 'none';
element.style.display = 'none'; }
} });
}); </script>
</script> </div>
</div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}} {{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{!-- Details Tab --}} {{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
<div class="tab details" data-group="primary" data-tab="details"> </div>
<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>
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section> </section>
</form> </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"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav> </nav>
{{!-- Item Sheet Body --}} {{!-- Item Sheet Body --}}
@ -69,5 +70,11 @@
{{!-- Item Action Template --}} {{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}} {{> "systems/sw5e/templates/items/parts/item-action.html"}}
</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> </section>
</form> </form>

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}} {{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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> --> <!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav> </nav>
@ -36,36 +37,11 @@
{{editor content=data.description.value target="data.description.value" button=true editable=editable}} {{editor content=data.description.value target="data.description.value" button=true editable=editable}}
</div> </div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
{{!-- 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>
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section> </section>
</form> </form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}} {{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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> --> <!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav> </nav>
@ -36,36 +37,11 @@
{{editor content=data.description.value target="data.description.value" button=true editable=editable}} {{editor content=data.description.value target="data.description.value" button=true editable=editable}}
</div> </div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
{{!-- 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>
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section> </section>
</form> </form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}} {{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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> --> <!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav> </nav>
@ -36,36 +37,11 @@
{{editor content=data.description.value target="data.description.value" button=true editable=editable}} {{editor content=data.description.value target="data.description.value" button=true editable=editable}}
</div> </div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
{{!-- 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>
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section> </section>
</form> </form>

View file

@ -25,8 +25,26 @@
</div> </div>
</header> </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 --}} {{!-- Item Sheet Body --}}
<section class="sheet-body"> <section class="sheet-body">
{{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- 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> </section>
</form> </form>

View file

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

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a> <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="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav> </nav>
{{!-- Item Sheet Body --}} {{!-- Item Sheet Body --}}
@ -68,6 +69,30 @@
</select> </select>
</div> </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 --}} {{!-- Preparation Mode --}}
<div class="form-group input-select"> <div class="form-group input-select">
<label>{{ localize "SW5E.PowerPreparationMode" }}</label> <label>{{ localize "SW5E.PowerPreparationMode" }}</label>
@ -76,24 +101,10 @@
{{ localize "SW5E.PowerPrepared" }} <input type="checkbox" name="data.preparation.prepared" {{checked data.preparation.prepared}}/> {{ localize "SW5E.PowerPrepared" }} <input type="checkbox" name="data.preparation.prepared" {{checked data.preparation.prepared}}/>
</label> </label>
<select name="data.preparation.mode"> <select name="data.preparation.mode">
{{#select data.preparation.mode}} {{ selectOptions config.powerPreparationModes selected=data.preparation.mode }}
<option value=""></option>
{{#each config.powerPreparationModes as |name key|}}
<option value="{{key}}">{{name}}</option>
{{/each}}
{{/select}}
</select> </select>
</div> </div>
</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> <h3 class="form-header">{{ localize "SW5E.PowerCastingHeader" }}</h3>
@ -120,5 +131,10 @@
</div> </div>
</div> </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> </section>
</form> </form>

View file

@ -26,6 +26,7 @@
<nav class="sheet-navigation tabs" data-group="primary"> <nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">Description</a> <a class="item active" data-tab="description">Description</a>
<a class="item" data-tab="species-traits">Species Traits</a> <a class="item" data-tab="species-traits">Species Traits</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav> </nav>
{{!-- Item Sheet Body --}} {{!-- Item Sheet Body --}}
@ -110,8 +111,11 @@
{{editor content=data.traits.value target="data.traits.value" button=true editable=editable}} {{editor content=data.traits.value target="data.traits.value" button=true editable=editable}}
</span> </span>
</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> </section>
</form> </form>

View file

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