System 1.1.1 ** Requires Foundry 0.7.6

System main update to be inline with dnd5e 1.1.1

Added active effects to as many sheets as I thought applicable. Please check loot, I made an attempt but it may be broken

All .less .css and actor .html updates were made to the old actors.  New actors may be broken with this update

removed templates\actors\oldActor\parts\actor-effects.html for newer templates\actors\parts\active-effects.html

removed module\apps\cast-dialog, templates\apps\cast-cast.html, and templates\items\cast.html.  I do not think they are used, I think they were deprecated when powers were treated as items, if not we can add them back in.

**NOTE** REQUIRES Foundry 0.7.6
This commit is contained in:
supervj 2020-11-12 17:30:07 -05:00
parent 27f5fa3670
commit 68a1b6a9f0
58 changed files with 1417 additions and 1706 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -68,7 +68,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} },
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
};
// Partition items by category
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 ) {
i.data.quantity = i.data.quantity || 0;
i.data.weight = i.data.weight || 0;
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1);
inventory[i.type].items.push(i);
}
@ -123,12 +123,12 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
const features = {
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: 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 },
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: 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 },
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true },
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: 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 },
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: 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 },
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"} },
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);
features.classes.items = classes;
features.classfeatures.items = classfeatures;
features.archetype.items = archetypes;
features.species.items = species;
features.background.items = backgrounds;
features.fightingstyles.items = fightingstyles;
features.fightingmasteries.items = fightingmasteries;
features.lightsaberforms.items = lightsaberforms;
features.classfeatures.items = classfeatures;
features.archetype.items = archetypes;
features.species.items = species;
features.background.items = backgrounds;
features.fightingstyles.items = fightingstyles;
features.fightingmasteries.items = fightingmasteries;
features.lightsaberforms.items = lightsaberforms;
// Assign and return
data.inventory = Object.values(inventory);
@ -189,9 +189,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
super.activateListeners(html);
if ( !this.options.editable ) return;
// Inventory Functions
html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
// Item State Toggling
html.find('.item-toggle').click(this._onToggleItem.bind(this));
@ -199,8 +196,8 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
html.find('.short-rest').click(this._onShortRest.bind(this));
html.find('.long-rest').click(this._onLongRest.bind(this));
// Death saving throws
html.find('.death-save').click(this._onDeathSave.bind(this));
// Rollable sheet actions
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
}
/* -------------------------------------------- */
@ -210,14 +207,19 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
* @param {MouseEvent} event The originating click event
* @private
*/
_onDeathSave(event) {
_onSheetAction(event) {
event.preventDefault();
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
* @param {Event} event The triggering click event
@ -259,53 +261,39 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
/* -------------------------------------------- */
/**
* Handle mouse click events to convert currency to the highest possible denomination
* @param {MouseEvent} event The originating click event
* @private
*/
async _onConvertCurrency(event) {
event.preventDefault();
return Dialog.confirm({
title: `${game.i18n.localize("SW5E.CurrencyConvert")}`,
content: `<p>${game.i18n.localize("SW5E.CurrencyConvertHint")}</p>`,
yes: () => this.actor.convertCurrency()
});
}
/* -------------------------------------------- */
/** @override */
async _onDropItemCreate(itemData) {
let addLevel = false;
// Upgrade the number of class levels a character has and add features
if ( itemData.type === "class" ) {
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
const classWasAlreadyPresent = !!cls;
let priorLevel = cls?.data.data.levels ?? 0;
const hasClass = !!cls;
// Add new features for class level
if ( !classWasAlreadyPresent ) {
Actor5e.getClassFeatures(itemData).then(features => {
this.actor.createEmbeddedEntity("OwnedItem", features);
});
// Increment levels instead of creating a new item
if ( hasClass ) {
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
if ( next > priorLevel ) {
itemData.levels = next;
await cls.update({"data.levels": next});
addLevel = true;
}
}
// If the actor already has the class, increment the level instead of creating a new item
// then add new features as long as level increases
if ( classWasAlreadyPresent ) {
const lvl = cls.data.data.levels;
const newLvl = Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level);
if ( !(lvl === newLvl) ) {
cls.update({"data.levels": newLvl});
itemData.data.levels = newLvl;
Actor5e.getClassFeatures(itemData).then(features => {
this.actor.createEmbeddedEntity("OwnedItem", features);
});
}
return
// Add class features
if ( !hasClass || addLevel ) {
const features = await Actor5e.getClassFeatures({
className: itemData.name,
subclassName: itemData.data.subclass,
level: itemData.levels,
priorLevel: priorLevel
});
await this.actor.createEmbeddedEntity("OwnedItem", features);
}
}
super._onDropItemCreate(itemData);
// Default drop handling if levels were not added
if ( !addLevel ) super._onDropItemCreate(itemData);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,7 @@ export default class ShortRestDialog extends Dialog {
/** @override */
getData() {
const data = super.getData();
// Determine Hit Dice
data.availableHD = this.actor.data.items.reduce((hd, item) => {
if ( item.type === "class" ) {
@ -49,7 +49,7 @@ export default class ShortRestDialog extends Dialog {
}, {});
data.canRoll = this.actor.data.data.attributes.hd > 0;
data.denomination = this._denom;
// Determine rest type
const variant = game.settings.get("sw5e", "restVariant");
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];
// Ensure it is an un-modified d20 roll
const isD20 = (d.faces === 20) && ( d.results.length === 1 );
const isD20 = (d.faces === 20) && ( d.values.length === 1 );
if ( !isD20 ) return;
const isModifiedRoll = ("success" in d.results[0]) || d.options.marginSuccess || d.options.marginFailure;
if ( isModifiedRoll ) return;
// Highlight successes and failures
if ( d.options.critical && (d.total >= d.options.critical) ) html.find(".dice-total").addClass("critical");
else if ( d.options.fumble && (d.total <= d.options.fumble) ) html.find(".dice-total").addClass("fumble");
const critical = d.options.critical || 20;
const fumble = d.options.fumble || 1;
if ( d.total >= critical ) html.find(".dice-total").addClass("critical");
else if ( d.total <= fumble ) html.find(".dice-total").addClass("fumble");
else if ( d.options.target ) {
if ( roll.total >= d.options.target ) html.find(".dice-total").addClass("success");
else html.find(".dice-total").addClass("failure");
@ -33,7 +35,8 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
export const displayChatActionButtons = function(message, html, data) {
const chatCard = html.find(".sw5e.chat-card");
if ( chatCard.length > 0 ) {
html.find(".flavor-text").remove();
const flavor = html.find(".flavor-text");
if ( flavor.text() === html.find(".item-name").text() ) flavor.remove();
// If the user is the message author or the actor owner, proceed
let actor = game.actors.get(data.message.speaker.actor);

View file

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

View file

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

View file

@ -1,9 +1,9 @@
/**
* 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, Advantage, or Disadvantage respectively
*
/**
* 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, Advantage, or Disadvantage respectively
*
* @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 {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
*
* @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,
flavor=null, fastForward=null, dialogOptions,
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null,
elvenAccuracy=false, halflingLucky=false, reliableTalent=false,
chatMessage=true, messageData={}}={}) {
// Prepare Message Data
messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker();
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
parts = parts.concat(["@bonus"]);
// Handle fast-forward events
let adv = 0;
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
if (fastForward) {
if ( advantage || event.altKey ) adv = 1;
else if ( disadvantage || event.ctrlKey || event.metaKey ) adv = -1;
*/
export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null,
flavor=null, fastForward=null, dialogOptions,
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null,
elvenAccuracy=false, halflingLucky=false, reliableTalent=false,
chatMessage=true, messageData={}}={}) {
// Prepare Message Data
messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker();
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
parts = parts.concat(["@bonus"]);
// Handle fast-forward events
let adv = 0;
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
if (fastForward) {
if ( advantage || event.altKey ) 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
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";
}
// 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";
}
// 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
let formula = `${nd}d20${mods}`;
if (reliableTalent) formula = `{${nd}d20${mods},10}kh`;
parts.unshift(formula);
// Optionally include a situational bonus
if ( form ) {
data['bonus'] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value;
}
if (!data["bonus"]) parts.pop();
// Optionally include an ability score selection (used for tool checks)
const ability = form ? form.ability : null;
if (ability && ability.value) {
data.ability = ability.value;
const abl = data.abilities[data.ability];
if (abl) {
data.mod = abl.mod;
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
}
// Optionally include a situational bonus
if ( form ) {
data['bonus'] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value;
}
if (!data["bonus"]) parts.pop();
// Optionally include an ability score selection (used for tool checks)
const ability = form ? form.ability : null;
if (ability && ability.value) {
data.ability = ability.value;
const abl = data.abilities[data.ability];
if (abl) {
data.mod = abl.mod;
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
}
}
// Execute the roll
let roll = new Roll(parts.join(" + "), data);
@ -139,73 +139,76 @@
*/
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) {
// Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = {
formula: parts.join(" + "),
data: data,
rollMode: rollMode,
rollModes: CONFIG.Dice.rollModes,
config: CONFIG.SW5E
};
const html = await renderTemplate(template, dialogData);
// Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = {
formula: parts.join(" + "),
data: data,
rollMode: rollMode,
rollModes: CONFIG.Dice.rollModes,
config: CONFIG.SW5E
};
const html = await renderTemplate(template, dialogData);
// Create the Dialog window
return new Promise(resolve => {
new Dialog({
title: title,
content: html,
buttons: {
advantage: {
label: game.i18n.localize("SW5E.Advantage"),
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")))
}
// Create the Dialog window
return new Promise(resolve => {
new Dialog({
title: title,
content: html,
buttons: {
advantage: {
label: game.i18n.localize("SW5E.Advantage"),
callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
},
default: "normal",
close: () => resolve(null)
}, dialogOptions).render(true);
});
}
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",
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
messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker();
@ -213,8 +216,8 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
parts = parts.concat(["@bonus"]);
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
// Define inner roll function
const _roll = function(parts, crit, form) {
// Define inner roll function
const _roll = function(parts, crit, form) {
// Optionally include a situational bonus
if ( form ) {
@ -224,17 +227,17 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
if (!data["bonus"]) parts.pop();
// 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
if ( crit === true ) {
let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0;
let mult = 2;
// TODO Backwards compatibility - REMOVE LATER
if (isNewerVersion(game.data.version, "0.6.9")) roll.alter(mult, add);
else roll.alter(add, mult);
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
roll.alter(criticalMultiplier, 0); // Multiply all dice
if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term
roll.terms[0].alter(1, criticalBonusDice);
roll._formula = roll.formula;
}
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
}
// 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
if ( itemData.type === "power" ) {
data.preparation.mode = data.preparation.mode || "prepared";
labels.level = C.powerLevels[data.level];
labels.school = C.powerSchools[data.school];
labels.components = Object.entries(data.components).reduce((arr, c) => {
@ -180,33 +181,33 @@ export default class Item5e extends Item {
else labels.featType = game.i18n.localize("SW5E.Passive");
}
// Species Items
else if ( itemData.type === "species" ) {
// labels.species = C.species[data.species];
}
// Archetype Items
else if ( itemData.type === "archetype" ) {
// Species Items
else if ( itemData.type === "species" ) {
// labels.species = C.species[data.species];
}
// Archetype Items
else if ( itemData.type === "archetype" ) {
// labels.archetype = C.archetype[data.archetype];
}
// Background Items
else if ( itemData.type === "background" ) {
}
// Class Feature Items
// Background Items
else if ( itemData.type === "background" ) {
// labels.background = C.background[data.background];
}
// Class Feature Items
else if ( itemData.type === "classfeature" ) {
// labels.classFeature = C.classFeature[data.classFeature];
}
// Fighting Style Items
else if ( itemData.type === "fightingstyle" ) {
// labels.fightingstyle = C.fightingstyle[data.fightingstyle];
}
// Fighting Mastery Items
else if ( itemData.type === "fightingmastery" ) {
// labels.fightingmastery = C.fightingmastery[data.fightingmastery];
}
// Lightsaber Form Items
else if ( itemData.type === "lightsaberform" ) {
// labels.lightsaberform = C.lightsaberform[data.lightsaberform];
}
// Equipment Items
@ -322,7 +323,7 @@ export default class Item5e extends Item {
user: game.user._id,
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
content: html,
flavor: this.name,
flavor: this.data.data.chatFlavor || this.name,
speaker: {
actor: this.actor._id,
token: this.actor.token,
@ -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.
* There are four types of ability consumptions which are handled:
* 1. Ammunition (on attack rolls)
@ -367,7 +368,6 @@ export default class Item5e extends Item {
if ( !consume.type ) return true;
const actor = this.actor;
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
const amount = parseInt(consume.amount || 1);
// Only handle certain types for certain actions
if ( ((consume.type === "ammo") && !isAttack ) || ((consume.type !== "ammo") && !isCard) ) return true;
@ -380,6 +380,7 @@ export default class Item5e extends Item {
// Identify the consumed resource and it's quantity
let consumed = null;
let amount = parseInt(consume.amount || 1);
let quantity = 0;
switch ( consume.type ) {
case "attribute":
@ -393,7 +394,13 @@ export default class Item5e extends Item {
break;
case "charges":
consumed = actor.items.get(consume.target);
quantity = consumed ? consumed.data.data.uses.value : 0;
if ( !consumed ) break;
const uses = consumed.data.data.uses;
if ( uses.per && uses.max ) quantity = uses.value;
else if ( consumed.data.data.recharge?.value ) {
quantity = consumed.data.data.recharge.charged ? 1 : 0;
amount = 1;
}
break;
}
@ -418,7 +425,11 @@ export default class Item5e extends Item {
await consumed.update({"data.quantity": remaining});
break;
case "charges":
await consumed.update({"data.uses.value": remaining});
const uses = consumed.data.data.uses || {};
const recharge = consumed.data.data.recharge || {};
if ( uses.per && uses.max ) await consumed.update({"data.uses.value": remaining});
else if ( recharge.value ) await consumed.update({"data.recharge.charged": false});
break;
}
return true;
}
@ -436,7 +447,7 @@ export default class Item5e extends Item {
// Configure whether to consume a limited use or to place a template
const charge = this.data.data.recharge;
const uses = this.data.data.uses;
let usesCharges = !!uses.per && (uses.max > 0);
let usesCharges = !!uses.per && !!uses.max;
let placeTemplate = false;
let consume = charge.value || usesCharges;
@ -637,40 +648,37 @@ export default class Item5e extends Item {
}
// Attack Bonus
if ( itemData.attackBonus ) parts.push(itemData.attackBonus);
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
if ( itemData.attackBonus || actorBonus.attack ) {
parts.push("@atk");
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
}
if ( actorBonus.attack ) parts.push(actorBonus.attack);
// Ammunition Bonus
delete this._ammo;
const consume = itemData.consume;
if ( consume?.type === "ammo" ) {
const ammo = this.actor.items.get(consume.target);
if(ammo?.data){
const q = ammo.data.data.quantity;
const consumeAmount = consume.amount ?? 0;
if ( q && (q - consumeAmount >= 0) ) {
let ammoBonus = ammo.data.data.attackBonus;
if ( ammoBonus ) {
parts.push("@ammo");
rollData["ammo"] = ammoBonus;
title += ` [${ammo.name}]`;
this._ammo = ammo;
}
// Ammunition Bonus
delete this._ammo;
const consume = itemData.consume;
if ( consume?.type === "ammo" ) {
const ammo = this.actor.items.get(consume.target);
if(ammo?.data){
const q = ammo.data.data.quantity;
const consumeAmount = consume.amount ?? 0;
if ( q && (q - consumeAmount >= 0) ) {
this._ammo = ammo;
let ammoBonus = ammo.data.data.attackBonus;
if ( ammoBonus ) {
parts.push("@ammo");
rollData["ammo"] = ammoBonus;
title += ` [${ammo.name}]`;
}
//}else{
// ui.notifications.error(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
}
}
}
// Compose roll options
const rollConfig = mergeObject({
parts: parts,
actor: this.actor,
data: rollData,
title: title,
flavor: title,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: {
width: 400,
@ -681,9 +689,11 @@ export default class Item5e extends Item {
}, options);
rollConfig.event = options.event;
// Expanded weapon critical threshold
// Expanded critical hit thresholds
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
rollConfig.critical = parseInt(flags.weaponCriticalThreshold);
} else if (( this.data.type === "power" ) && flags.powerCriticalThreshold) {
rollConfig.critical = parseInt(flags.powerCriticalThreshold);
}
// Elven Accuracy
@ -710,28 +720,41 @@ export default class Item5e extends Item {
/**
* Place a damage roll using an item (weapon, feat, power, or equipment)
* Rely upon the damageRoll logic for the core implementation
*
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
* Rely upon the damageRoll logic for the core implementation.
* @param {MouseEvent} [event] An event which triggered this roll, if any
* @param {number} [powerLevel] If the item is a power, override the level for damage scaling
* @param {boolean} [versatile] If the item is a weapon, roll damage using the versatile formula
* @param {object} [options] Additional options passed to the damageRoll function
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
*/
rollDamage({event, powerLevel=null, versatile=false}={}) {
rollDamage({event, powerLevel=null, versatile=false, options={}}={}) {
if ( !this.hasDamage ) throw new Error("You may not make a Damage Roll with this Item.");
const itemData = this.data.data;
const actorData = this.actor.data.data;
if ( !this.hasDamage ) {
throw new Error("You may not make a Damage Roll with this Item.");
}
const messageData = {"flags.sw5e.roll": {type: "damage", itemId: this.id }};
// Get roll data
const parts = itemData.damage.parts.map(d => d[0]);
const rollData = this.getRollData();
if ( powerLevel ) rollData.item.level = powerLevel;
// Get message labels
// Configure the damage roll
const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`;
let flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title;
// Define Roll parts
const parts = itemData.damage.parts.map(d => d[0]);
const rollConfig = {
event: event,
parts: parts,
actor: this.actor,
data: rollData,
title: title,
flavor: this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: {
width: 400,
top: event ? event.clientY - 80 : null,
left: window.innerWidth - 710
},
messageData: messageData
};
// Adjust damage from versatile usage
if ( versatile && itemData.damage.versatile ) {
@ -751,37 +774,27 @@ export default class Item5e extends Item {
}
}
// Define Roll Data
// Add damage bonus formula
const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) {
parts.push("@dmg");
rollData["dmg"] = actorBonus.damage;
if ( actorBonus.damage && (parseInt(actorBonus.damage) !== 0) ) {
parts.push(actorBonus.damage);
}
// Ammunition Damage
// Add ammunition damage
if ( this._ammo ) {
parts.push("@ammo");
rollData["ammo"] = this._ammo.data.data.damage.parts.map(p => p[0]).join("+");
flavor += ` [${this._ammo.name}]`;
rollConfig.flavor += ` [${this._ammo.name}]`;
delete this._ammo;
}
// Scale melee critical hit damage
if ( itemData.actionType === "mwak" ) {
rollConfig.criticalBonusDice = this.actor.getFlag("sw5e", "meleeCriticalDamageDice") ?? 0;
}
// Call the roll helper utility
return damageRoll({
event: event,
parts: parts,
actor: this.actor,
data: rollData,
title: title,
flavor: flavor,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: {
width: 400,
top: event ? event.clientY - 80 : null,
left: window.innerWidth - 710
},
messageData
});
return damageRoll(mergeObject(rollConfig, options));
}
/* -------------------------------------------- */
@ -793,22 +806,7 @@ export default class Item5e extends Item {
_scaleAtWillDamage(parts, scale, level, rollData) {
const add = Math.floor((level + 1) / 6);
if ( add === 0 ) return;
// FUTURE SOLUTION - 0.7.0 AND LATER
if (isNewerVersion(game.data.version, "0.6.9")) {
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData)
}
// LEGACY SOLUTION - 0.6.x AND OLDER
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
else {
if ( scale && (scale !== parts[0]) ) {
parts[0] = parts[0] + " + " + scale.replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${add}d${d}`);
} else {
parts[0] = parts[0].replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${parseInt(nd)+add}d${d}`);
}
}
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData);
}
/* -------------------------------------------- */
@ -826,20 +824,7 @@ export default class Item5e extends Item {
_scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) {
const upcastLevels = Math.max(powerLevel - baseLevel, 0);
if ( upcastLevels === 0 ) return parts;
// FUTURE SOLUTION - 0.7.0 AND LATER
if (isNewerVersion(game.data.version, "0.6.9")) {
this._scaleDamage(parts, formula, upcastLevels, rollData);
}
// LEGACY SOLUTION - 0.6.x AND OLDER
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
else {
const bonus = new Roll(formula);
bonus.alter(0, upcastLevels);
parts.push(bonus.formula);
}
return parts;
this._scaleDamage(parts, formula, upcastLevels, rollData);
}
/* -------------------------------------------- */
@ -882,7 +867,7 @@ export default class Item5e extends Item {
/**
* Place an attack roll using an item (weapon, feat, power, or equipment)
* Rely upon the d20Roll logic for the core implementation
*
*
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
*/
async rollFormula(options={}) {
@ -899,7 +884,7 @@ export default class Item5e extends Item {
const roll = new Roll(rollData.item.formula, rollData).roll();
roll.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: this.data.data.chatFlavor || title,
flavor: title,
rollMode: game.settings.get("core", "rollMode"),
messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }}
});
@ -910,7 +895,7 @@ export default class Item5e extends 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
* @private
*/
@ -972,7 +957,7 @@ export default class Item5e extends Item {
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
}
return true;
}
}
/* -------------------------------------------- */
@ -1021,7 +1006,7 @@ export default class Item5e extends Item {
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
title: title,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`,
flavor: title,
dialogOptions: {
width: 400,
top: options.event ? options.event.clientY - 80 : null,
@ -1055,8 +1040,8 @@ export default class Item5e extends Item {
}
// Include a proficiency score
const prof = "proficient" in rollData.item ? (rollData.item.proficient || 0) : 1;
rollData["prof"] = Math.floor(prof * rollData.attributes.prof);
const prof = ("proficient" in rollData.item) ? (rollData.item.proficient || 0) : 1;
rollData["prof"] = Math.floor(prof * (rollData.attributes.prof || 0));
return rollData;
}
@ -1189,7 +1174,7 @@ export default class Item5e extends Item {
if ( !targets.length ) ui.notifications.warn(game.i18n.localize("SW5E.ActionWarningNoToken"));
return targets;
}
/* -------------------------------------------- */
/* Factory Methods */
/* -------------------------------------------- */

View file

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

View file

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

View file

@ -37,7 +37,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
templateData.width = target.value;
templateData.direction = 45;
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;
break;
default:

View file

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

View file

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

257
sw5e.css
View file

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

51
sw5e.js
View file

@ -29,6 +29,7 @@ import ActorSheet5eCharacterNew from "./module/actor/sheets/newSheet/character.j
import ItemSheet5e from "./module/item/sheet.js";
import ShortRestDialog from "./module/apps/short-rest.js";
import TraitSelector from "./module/apps/trait-selector.js";
import MovementConfig from "./module/apps/movement-config.js";
// Import Helpers
import * as chat from "./module/chat.js";
@ -54,7 +55,8 @@ Hooks.once("init", function() {
ActorSheet5eVehicle,
ItemSheet5e,
ShortRestDialog,
TraitSelector
TraitSelector,
MovementConfig
},
canvas: {
AbilityTemplate
@ -74,7 +76,7 @@ Hooks.once("init", function() {
CONFIG.SW5E = SW5E;
CONFIG.Actor.entityClass = Actor5e;
CONFIG.Item.entityClass = Item5e;
if ( CONFIG.time ) CONFIG.time.roundTime = 6; // TODO remove conditional after 0.7.x
CONFIG.time.roundTime = 6;
// Add DND5e namespace for module compatability
game.dnd5e = game.sw5e;
@ -132,18 +134,18 @@ Hooks.once("setup", function() {
// Localize CONFIG objects once up-front
const toLocalize = [
"abilities", "abilityAbbreviations", "alignments", "conditionTypes", "consumableTypes", "currencies",
"damageTypes", "damageResistanceTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes",
"limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes", "powerSchools",
"powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages",
"polymorphSettings", "armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes",
"abilityConsumptionTypes", "actorSizes", "proficiencyLevels", "armorPropertiesTypes", "cover"
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments",
"armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes",
"damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages",
"limitedUsePeriods", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills",
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes",
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes"
];
// Exclude some from sorting where the default order matters
const noSort = [
"abilities", "alignments", "currencies", "distanceUnits", "itemActionTypes", "proficiencyLevels",
"limitedUsePeriods", "powerComponents", "powerLevels", "weaponTypes"
"abilities", "alignments", "currencies", "distanceUnits", "movementUnits", "itemActionTypes", "proficiencyLevels",
"limitedUsePeriods", "powerComponents", "powerLevels", "powerPreparationModes", "weaponTypes"
];
// Localize and sort CONFIG objects
@ -171,22 +173,23 @@ Hooks.once("setup", function() {
*/
Hooks.once("ready", function() {
// Determine whether a system migration is required and feasible
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
const NEEDS_MIGRATION_VERSION = 0.84;
const COMPATIBLE_MIGRATION_VERSION = 0.80;
let needMigration = (currentVersion < NEEDS_MIGRATION_VERSION) || (currentVersion === null);
// Perform the migration
if ( needMigration && game.user.isGM ) {
if ( currentVersion && (currentVersion < COMPATIBLE_MIGRATION_VERSION) ) {
ui.notifications.error(`Your SW5E system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`, {permanent: true});
}
migrations.migrateWorld();
}
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot));
// Determine whether a system migration is required and feasible
if ( !game.user.isGM ) return;
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
const NEEDS_MIGRATION_VERSION = "1.1.0";
const COMPATIBLE_MIGRATION_VERSION = 0.80;
const needsMigration = currentVersion && isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion);
if ( !needsMigration ) return;
// Perform the migration
if ( currentVersion && isNewerVersion(COMPATIBLE_MIGRATION_VERSION, currentVersion) ) {
const warning = `Your SW5e system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`;
ui.notifications.error(warning, {permanent: true});
}
migrations.migrateWorld();
});
/* -------------------------------------------- */

View file

@ -2,7 +2,7 @@
"name": "sw5e",
"title": "SW 5th Edition",
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.",
"version": 0.98,
"version": "1.1.1",
"author": "Dev Team",
"scripts": [],
"esmodules": ["sw5e.js"],
@ -56,17 +56,17 @@
"path": "./packs/packs/feats.db",
"entity": "Item"
},
{
{
"name": "fightingstyles",
"label": "Fighting Styles",
"path": "./packs/packs/fightingstyles.db",
"entity": "Item"
"label": "Fighting Styles",
"path": "./packs/packs/fightingstyles.db",
"entity": "Item"
},
{
{
"name": "fightingmasteries",
"label": "Fighting Masteries",
"path": "./packs/packs/fightingmasteries.db",
"entity": "Item"
"label": "Fighting Masteries",
"path": "./packs/packs/fightingmasteries.db",
"entity": "Item"
},
{
"name": "forcepowers",
@ -103,20 +103,20 @@
"label": "Species Traits",
"path": "./packs/packs/speciestraits.db",
"entity": "Item"
},
{
},
{
"name": "tables",
"label": "Tables",
"path": "./packs/packs/tables.db",
"entity": "RollTable"
},
{
},
{
"name": "techpowers",
"label": "Tech Powers",
"path": "./packs/packs/techpowers.db",
"entity": "Item"
},
{
},
{
"name": "weapons",
"label": "Weapons",
"path": "./packs/packs/weapons.db",
@ -135,8 +135,8 @@
"gridUnits": "ft",
"primaryTokenAttribute": "attributes.hp",
"secondaryTokenAttribute": null,
"minimumCoreVersion": "0.5.6",
"compatibleCoreVersion": "0.7.5",
"minimumCoreVersion": "0.7.6",
"compatibleCoreVersion": "0.7.6",
"url": "https://github.com/unrealkakeman89/sw5e",
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"

View file

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

View file

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

View file

@ -62,14 +62,14 @@
</li>
<li class="attribute">
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
<h4 class="attribute-name box-title">
{{ localize "SW5E.Speed" }}
</h4>
<div class="attribute-value">
<input name="data.attributes.speed.value" type="text"
value="{{data.attributes.speed.value}}" placeholder="0"/>
<span>{{movement.primary}}</span>
</div>
<footer class="attribute-footer">
<input type="text" class="speed" name="data.attributes.speed.special"
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
<span>{{movement.special}}</span>
</footer>
</li>
</ul>
@ -81,6 +81,7 @@
<a class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</a>
<a class="item" data-tab="features">{{ localize "SW5E.Features" }}</a>
<a class="item" data-tab="powerbook">{{ localize "SW5E.Powerbook" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<a class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</a>
</nav>
@ -143,24 +144,29 @@
<div class="counter-value">
<input name="data.resources.lair.value" type="checkbox" value="{{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"/>
</div>
<input name="data.resources.lair.initiative" type="number" value="{{data.resources.lair.initiative}}" placeholder="20"/>
</div>
</div>
</div>
{{!-- Traits --}}
{{> "systems/sw5e/templates/actors/parts/actor-traits.html"}}
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-traits.html"}}
</section>
</div>
{{!-- Features Tab --}}
<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>
{{!-- Powerbook Tab --}}
<div class="tab powerbook flexcol" data-group="primary" data-tab="powerbook">
{{> "systems/sw5e/templates/actors/parts/actor-powerbook.html"}}
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
{{!-- Biography Tab --}}
@ -168,4 +174,4 @@
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
</div>
</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>
{{/unless}}
<ol class="inventory-list">
<ol class="items-list inventory-list">
{{#each sections as |section sid|}}
<li class="inventory-header flexrow">
<li class="items-header flexrow">
<h3 class="item-name flexrow">{{localize section.label}}</h3>
{{#if section.hasActions}}
@ -25,7 +25,7 @@
{{/if}}
{{#if ../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
<a class="item-control item-create" title="{{localize 'SW5E.FeatureAdd'}}" {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
</a>
@ -78,7 +78,7 @@
<div class="item-detail player-classfeatures">
{{item.data.name}}
</div>
{{else if section.isFightingstyles}}
<div class="item-detail player-fightingstyles">
{{item.data.name}}
@ -119,7 +119,7 @@
{{/if}}
{{#if ../../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
{{#if section.crewable}}
<a class="item-control item-toggle {{item.toggleClass}}"
title="{{item.toggleTitle}}">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,44 +1,48 @@
<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|}}
<h3 class="form-header">{{localize section}}</h3>
{{#each fs as |flag key|}}
{{#each flags as |fs section|}}
<h3 class="form-header">{{localize section}}</h3>
{{#each fs as |flag key|}}
<div class="form-group">
<label>{{localize flag.name}}</label>
<div class="form-group">
<label>{{localize flag.name}}</label>
{{#if flag.isCheckbox}}
<input type="checkbox" name="{{key}}" data-dtype="Boolean" {{checked flag.value}}/>
{{#if flag.isCheckbox}}
<input type="checkbox" name="{{key}}" data-dtype="Boolean" {{checked flag.value}}/>
{{else if flag.isSelect}}
<select name="{{key}}" data-dtype="{{flag.type}}">
{{#select flag.value}}
{{#each flag.choices as |v k|}}
<option value="{{k}}">{{localize v}}</option>
{{/each}}
{{/select}}
</select>
{{else if flag.isSelect}}
<select name="{{key}}" data-dtype="{{flag.type}}">
{{#select flag.value}}
{{#each flag.choices as |v k|}}
<option value="{{k}}">{{localize v}}</option>
{{/each}}
{{/select}}
</select>
{{else}}
<input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/>
{{/if}}
{{else}}
<input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/>
{{/if}}
<p class="notes">{{localize flag.hint}}</p>
</div>
{{/each}}
{{/each}}
<p class="notes">{{localize flag.hint}}</p>
</div>
{{/each}}
{{/each}}
<h3 class="form-header">{{localize "SW5E.Bonuses"}}</h3>
<p class="notes">{{localize "SW5E.BonusesHint"}}</p>
{{#each bonuses as |b|}}
<div class="form-group">
<label>{{localize b.label}}</label>
<input type="text" name="{{b.name}}" value="{{b.value}}"/>
</div>
{{/each}}
<h3 class="form-header">{{localize "SW5E.Bonuses"}}</h3>
<p class="notes">{{localize "SW5E.BonusesHint"}}</p>
{{#each bonuses as |b|}}
<div class="form-group">
<label>{{localize b.label}}</label>
<input type="text" name="{{b.name}}" value="{{b.value}}"/>
</div>
{{/each}}
</section>
<button type="submit" name="submit">
<i class="far fa-save"></i> {{localize 'SW5E.FlagsSave'}}
</button>
<footer class="form-footer">
<button type="submit" name="submit">
<i class="far fa-save"></i> {{localize 'SW5E.FlagsSave'}}
</button>
</footer>
</form>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,8 +25,26 @@
</div>
</header>
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
{{!-- Item Sheet Body --}}
<section class="sheet-body">
{{> "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>
</form>

View file

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

View file

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

View file

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

View file

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