forked from GitHub-Mirrors/foundry-sw5e
Merge branch 'Develop' into cbnathanael
This commit is contained in:
commit
2d8cc8cda2
58 changed files with 1414 additions and 1704 deletions
23
lang/en.json
23
lang/en.json
|
@ -263,6 +263,11 @@
|
|||
"SW5E.FeatureActionRecharge": "Action Recharge",
|
||||
"SW5E.Flaws": "Flaws",
|
||||
|
||||
"SW5E.EffectCreate": "Create Effect",
|
||||
"SW5E.EffectToggle": "Toggle Effect",
|
||||
"SW5E.EffectEdit": "Edit Effect",
|
||||
"SW5E.EffectDelete": "Delete Effect",
|
||||
|
||||
"SW5E.ItemTypeArchetype": "Archetype",
|
||||
"SW5E.ItemTypeBackground": "Background",
|
||||
"Sw5E.ItemTypeBackgroundPl": "Backgrounds",
|
||||
|
@ -332,6 +337,12 @@
|
|||
"SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
|
||||
"SW5E.FlagsCritThreshold": "Critical Hit Threshold",
|
||||
"SW5E.FlagsCritThresholdHint": "Allow for expanded critical range; for example Improved or Superior Critical",
|
||||
"SW5E.FlagsWeaponCritThreshold": "Weapon Critical Hit Threshold",
|
||||
"SW5E.FlagsWeaponCritThresholdHint": "An expanded critical hit threshold for weapon attacks.",
|
||||
"SW5E.FlagsPowerCritThreshold": "Power Critical Hit Threshold",
|
||||
"SW5E.FlagsPowerCritThresholdHint": "An expanded critical hit threshold for power attacks.",
|
||||
"SW5E.FlagsMeleeCriticalDice": "Melee Critical Damage Dice",
|
||||
"SW5E.FlagsMeleeCriticalDiceHint": "A number of additional damage dice added to melee weapon critical hits.",
|
||||
|
||||
"SW5E.Flat": "Flat",
|
||||
"SW5E.Formula": "Formula",
|
||||
|
@ -582,6 +593,17 @@
|
|||
"SW5E.RollMode": "Roll Mode",
|
||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||
"SW5E.Save": "Save",
|
||||
|
||||
"SW5E.MovementConfig": "Configure Movement Speed",
|
||||
"SW5E.MovementConfigHint": "Configure the movement speed and special movement attributes of this creature.",
|
||||
"SW5E.MovementWalk": "Walk",
|
||||
"SW5E.MovementBurrow": "Burrow",
|
||||
"SW5E.MovementClimb": "Climb",
|
||||
"SW5E.MovementHover": "Hover",
|
||||
"SW5E.MovementFly": "Fly",
|
||||
"SW5E.MovementSwim": "Swim",
|
||||
"SW5E.MovementUnits": "Units",
|
||||
|
||||
"SW5E.SheetClassCharacter": "Default Character Sheet",
|
||||
"SW5E.SheetClassCharacterOld": "Old Character Sheet",
|
||||
"SW5E.SheetClassNPC": "Default NPC Sheet",
|
||||
|
@ -837,6 +859,7 @@
|
|||
"SW5E.available": "available",
|
||||
"SW5E.description": "A comprehensive game system for running games of Star Wars 5th Edition in the Foundry VTT environment.",
|
||||
"SW5E.of": "of",
|
||||
"SW5E.per": "per",
|
||||
"SW5E.power": "power",
|
||||
"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.",
|
||||
"SETTINGS.5eAllowPolymorphingN": "Allow Polymorphing",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -70,7 +70,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
32
module/apps/movement-config.js
Normal file
32
module/apps/movement-config.js
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
|
|
273
module/dice.js
273
module/dice.js
|
@ -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
63
module/effects.js
vendored
Normal 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;
|
||||
}
|
|
@ -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 */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
257
sw5e.css
|
@ -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
51
sw5e.js
|
@ -30,6 +30,7 @@ import ActorSheet5eNPCNew from "./module/actor/sheets/newSheet/npc.js";
|
|||
import ItemSheet5e from "./module/item/sheet.js";
|
||||
import ShortRestDialog from "./module/apps/short-rest.js";
|
||||
import TraitSelector from "./module/apps/trait-selector.js";
|
||||
import MovementConfig from "./module/apps/movement-config.js";
|
||||
|
||||
// Import Helpers
|
||||
import * as chat from "./module/chat.js";
|
||||
|
@ -56,7 +57,8 @@ Hooks.once("init", function() {
|
|||
ActorSheet5eVehicle,
|
||||
ItemSheet5e,
|
||||
ShortRestDialog,
|
||||
TraitSelector
|
||||
TraitSelector,
|
||||
MovementConfig
|
||||
},
|
||||
canvas: {
|
||||
AbilityTemplate
|
||||
|
@ -76,7 +78,7 @@ Hooks.once("init", function() {
|
|||
CONFIG.SW5E = SW5E;
|
||||
CONFIG.Actor.entityClass = Actor5e;
|
||||
CONFIG.Item.entityClass = Item5e;
|
||||
if ( CONFIG.time ) CONFIG.time.roundTime = 6; // TODO remove conditional after 0.7.x
|
||||
CONFIG.time.roundTime = 6;
|
||||
|
||||
// Add DND5e namespace for module compatability
|
||||
game.dnd5e = game.sw5e;
|
||||
|
@ -139,18 +141,18 @@ Hooks.once("setup", function() {
|
|||
|
||||
// Localize CONFIG objects once up-front
|
||||
const toLocalize = [
|
||||
"abilities", "abilityAbbreviations", "alignments", "conditionTypes", "consumableTypes", "currencies",
|
||||
"damageTypes", "damageResistanceTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes",
|
||||
"limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes", "powerSchools",
|
||||
"powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages",
|
||||
"polymorphSettings", "armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes",
|
||||
"abilityConsumptionTypes", "actorSizes", "proficiencyLevels", "armorPropertiesTypes", "cover"
|
||||
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments",
|
||||
"armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes",
|
||||
"damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages",
|
||||
"limitedUsePeriods", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills",
|
||||
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes",
|
||||
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes"
|
||||
];
|
||||
|
||||
// Exclude some from sorting where the default order matters
|
||||
const noSort = [
|
||||
"abilities", "alignments", "currencies", "distanceUnits", "itemActionTypes", "proficiencyLevels",
|
||||
"limitedUsePeriods", "powerComponents", "powerLevels", "weaponTypes"
|
||||
"abilities", "alignments", "currencies", "distanceUnits", "movementUnits", "itemActionTypes", "proficiencyLevels",
|
||||
"limitedUsePeriods", "powerComponents", "powerLevels", "powerPreparationModes", "weaponTypes"
|
||||
];
|
||||
|
||||
// Localize and sort CONFIG objects
|
||||
|
@ -178,22 +180,23 @@ Hooks.once("setup", function() {
|
|||
*/
|
||||
Hooks.once("ready", function() {
|
||||
|
||||
// Determine whether a system migration is required and feasible
|
||||
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
|
||||
const NEEDS_MIGRATION_VERSION = 0.84;
|
||||
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
||||
let needMigration = (currentVersion < NEEDS_MIGRATION_VERSION) || (currentVersion === null);
|
||||
|
||||
// Perform the migration
|
||||
if ( needMigration && game.user.isGM ) {
|
||||
if ( currentVersion && (currentVersion < COMPATIBLE_MIGRATION_VERSION) ) {
|
||||
ui.notifications.error(`Your SW5E system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`, {permanent: true});
|
||||
}
|
||||
migrations.migrateWorld();
|
||||
}
|
||||
|
||||
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
|
||||
Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot));
|
||||
|
||||
// Determine whether a system migration is required and feasible
|
||||
if ( !game.user.isGM ) return;
|
||||
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
|
||||
const NEEDS_MIGRATION_VERSION = "1.1.0";
|
||||
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
||||
const needsMigration = currentVersion && isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion);
|
||||
if ( !needsMigration ) return;
|
||||
|
||||
// Perform the migration
|
||||
if ( currentVersion && isNewerVersion(COMPATIBLE_MIGRATION_VERSION, currentVersion) ) {
|
||||
const warning = `Your SW5e system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`;
|
||||
ui.notifications.error(warning, {permanent: true});
|
||||
}
|
||||
migrations.migrateWorld();
|
||||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
34
system.json
34
system.json
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,8 +144,8 @@
|
|||
<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>
|
||||
|
||||
|
@ -163,9 +164,14 @@
|
|||
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}}
|
||||
</div>
|
||||
|
||||
{{!-- Effects Tab --}}
|
||||
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
|
||||
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
|
||||
</div>
|
||||
|
||||
{{!-- Biography Tab --}}
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</form>
|
||||
|
|
|
@ -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>
|
|
@ -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}}">
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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' }}"/>
|
||||
|
|
|
@ -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="—"
|
||||
value="{{data.attributes.speed.value}}">
|
||||
<input name="data.attributes.speed" type="text" placeholder="—" 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">
|
||||
|
|
38
templates/actors/parts/active-effects.html
Normal file
38
templates/actors/parts/active-effects.html
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
34
templates/apps/movement-config.html
Normal file
34
templates/apps/movement-config.html
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> </p>
|
||||
<div class="medtable"><p>{{{data.idealOptions.value}}}</p></div><p> </p>
|
||||
<div class="medtable"><p>{{{data.flawOptions.value}}}</p></div><p> </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> </p>
|
||||
<div class="medtable"><p>{{{data.idealOptions.value}}}</p></div><p> </p>
|
||||
<div class="medtable"><p>{{{data.flawOptions.value}}}</p></div><p> </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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<nav class="sheet-navigation tabs" data-group="primary">
|
||||
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
|
||||
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
|
||||
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
|
||||
</nav>
|
||||
|
||||
{{!-- Item Sheet Body --}}
|
||||
|
@ -84,7 +85,6 @@
|
|||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.properties.{{prop}}" {{checked (lookup ../data.properties prop)}}/> {{ name }}
|
||||
</label>
|
||||
<label class="text"> <input type="text" maxlength="2" name="wpNum"/></label>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
|
@ -110,5 +110,10 @@
|
|||
{{!-- Item Action Template --}}
|
||||
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
|
||||
</div>
|
||||
|
||||
{{!-- Effects Tab --}}
|
||||
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
|
||||
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue