forked from GitHub-Mirrors/foundry-sw5e
DND5e Core 1.1.1
DND5e Core 1.1.1 with find and replace to SW5e
This commit is contained in:
parent
07a72da362
commit
a544f5e0a9
3107 changed files with 7609 additions and 9785 deletions
|
@ -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.
|
||||
*/
|
||||
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 ) {
|
||||
|
@ -100,10 +58,11 @@ export default class Actor5e extends Actor {
|
|||
}
|
||||
|
||||
// Ability modifiers and saves
|
||||
const dcBonus = Number.isNumeric(data.bonuses.power?.dc) ? parseInt(data.bonuses.power.dc) : 0;
|
||||
const dcBonus = Number.isNumeric(data.bonuses?.power?.dc) ? parseInt(data.bonuses.power.dc) : 0;
|
||||
const saveBonus = Number.isNumeric(bonuses.save) ? parseInt(bonuses.save) : 0;
|
||||
const checkBonus = Number.isNumeric(bonuses.check) ? parseInt(bonuses.check) : 0;
|
||||
for (let [id, abl] of Object.entries(data.abilities)) {
|
||||
if ( flags.diamondSoul ) abl.proficient = 1; // Diamond Soul is proficient in all saves
|
||||
abl.mod = Math.floor((abl.value - 10) / 2);
|
||||
abl.prof = (abl.proficient || 0) * data.attributes.prof;
|
||||
abl.saveBonus = saveBonus;
|
||||
|
@ -116,6 +75,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 +90,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 +134,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,32 +142,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} archetypeName The archetype 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="", archetypeName="", level=1, priorLevel=0}={}) {
|
||||
className = className.toLowerCase();
|
||||
archetypeName = archetypeName.slugify();
|
||||
|
||||
// Get the configuration of features which may be added
|
||||
const clsConfig = CONFIG.SW5E.classFeatures[className];
|
||||
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 archetype features
|
||||
const archConfig = clsConfig.archetypes[archetypeName] || {};
|
||||
for ( let [l, f] of Object.entries(archConfig.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 ) {
|
||||
|
@ -236,35 +205,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,
|
||||
archetypeName: updateData.data.archetype || item.data.data.archetype,
|
||||
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.archetypeName !== item.data.data.archetype ) 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
|
||||
}
|
||||
|
||||
|
@ -300,9 +262,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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -368,17 +327,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;
|
||||
|
@ -460,7 +419,7 @@ export default class Actor5e extends Actor {
|
|||
progression.slot = Math.ceil(caster.data.levels / denom);
|
||||
}
|
||||
|
||||
// EXCEPTION: NPC with an explicit powercaster level
|
||||
// EXCEPTION: NPC with an explicit power-caster level
|
||||
if (isNPC && actorData.data.details.powerLevel) {
|
||||
progression.slot = actorData.data.details.powerLevel;
|
||||
}
|
||||
|
@ -488,8 +447,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,14 +471,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
|
||||
|
@ -534,9 +493,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) };
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -563,9 +523,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")) ) {
|
||||
|
@ -576,7 +533,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);
|
||||
|
@ -857,7 +814,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};
|
||||
|
@ -913,6 +870,12 @@ export default class Actor5e extends Actor {
|
|||
const data = {};
|
||||
const speaker = options.speaker || ChatMessage.getSpeaker({actor: this});
|
||||
|
||||
// Diamond Soul adds proficiency
|
||||
if ( this.getFlag("sw5e", "diamondSoul") ) {
|
||||
parts.push("@prof");
|
||||
data.prof = this.data.data.attributes.prof;
|
||||
}
|
||||
|
||||
// Include a global actor ability save bonus
|
||||
const bonuses = getProperty(this.data.data, "bonuses.abilities") || {};
|
||||
if ( bonuses.save ) {
|
||||
|
@ -936,7 +899,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 ) {
|
||||
|
@ -1252,6 +1215,23 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Convert all carried currency to the highest possible denomination to reduce the number of raw coins being
|
||||
* carried by an Actor.
|
||||
* @return {Promise<Actor5e>}
|
||||
*/
|
||||
convertCurrency() {
|
||||
const curr = duplicate(this.data.data.currency);
|
||||
const convert = CONFIG.SW5E.currencyConversion;
|
||||
for ( let [c, t] of Object.entries(convert) ) {
|
||||
let change = Math.floor(curr[c] / t.each);
|
||||
curr[c] -= (change * t.each);
|
||||
curr[t.into] += change;
|
||||
}
|
||||
return this.update({"data.currency": curr});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Transform this Actor into another one.
|
||||
|
@ -1428,14 +1408,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;
|
||||
|
@ -1447,7 +1429,7 @@ export default class Actor5e extends Actor {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Add additional system-specific sidebar directory context menu options for SW5e Actor entities
|
||||
* Add additional system-specific sidebar directory context menu options for Actor entities
|
||||
* @param {jQuery} html The sidebar HTML
|
||||
* @param {Array} entryOptions The default array of context menu options
|
||||
*/
|
||||
|
|
|
@ -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 system-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,8 +106,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
// TODO Disabled until 0.7.5 release
|
||||
// this._prepareEffects(data);
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
|
@ -110,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
|
||||
|
@ -148,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.inactive.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
|
||||
|
@ -243,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]);
|
||||
|
@ -364,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 ) {
|
||||
|
@ -384,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
|
||||
|
@ -394,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
|
||||
|
@ -566,7 +555,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
|
@ -577,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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -732,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 new ActiveEffectConfig(effect).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
|
||||
|
@ -826,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();
|
||||
|
@ -840,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);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import ActorSheet5e from "./base.js";
|
|||
import Actor5e from "../entity.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for player character type actors in the SW5E system.
|
||||
* An Actor sheet for player character type actors.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @type {ActorSheet5e}
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "character"],
|
||||
width: 720,
|
||||
height: 736
|
||||
height: 680
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -68,9 +68,9 @@ 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] = data.items.reduce((arr, item) => {
|
||||
let [items, powers, feats, classes] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
|
@ -89,12 +89,9 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
if ( item.type === "power" ) arr[1].push(item);
|
||||
else if ( item.type === "feat" ) arr[2].push(item);
|
||||
else if ( item.type === "class" ) arr[3].push(item);
|
||||
else if ( item.type === "species" ) arr[4].push(item);
|
||||
else if ( item.type === "archetype" ) arr[5].push(item);
|
||||
else if ( item.type === "classfeature" ) arr[6].push(item);
|
||||
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
||||
return arr;
|
||||
}, [[], [], [], [], [], [], []]);
|
||||
}, [[], [], [], []]);
|
||||
|
||||
// Apply active item filters
|
||||
items = this._filterItems(items, this._filters.inventory);
|
||||
|
@ -105,7 +102,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);
|
||||
}
|
||||
|
||||
|
@ -118,9 +115,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
// Organize Features
|
||||
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},
|
||||
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
||||
};
|
||||
|
@ -130,9 +124,6 @@ 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;
|
||||
|
||||
// Assign and return
|
||||
data.inventory = Object.values(inventory);
|
||||
|
@ -177,9 +168,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));
|
||||
|
||||
|
@ -187,25 +175,36 @@ 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));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling a death saving throw for the Character
|
||||
* Handle mouse click events for character sheet actions
|
||||
* @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 "convertCurrency":
|
||||
return Dialog.confirm({
|
||||
title: `${game.i18n.localize("SW5E.CurrencyConvert")}`,
|
||||
content: `<p>${game.i18n.localize("SW5E.CurrencyConvertHint")}</p>`,
|
||||
yes: () => this.actor.convertCurrency()
|
||||
});
|
||||
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
|
||||
|
@ -247,54 +246,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
|
||||
// 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,
|
||||
archetypeName: itemData.data.archetype,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import ActorSheet5e from "../sheets/base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for NPC type characters in the SW5E system.
|
||||
* An Actor sheet for NPC type characters.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @extends {ActorSheet5e}
|
||||
*/
|
||||
|
@ -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() {
|
||||
|
@ -72,27 +67,24 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
{name: "data.bonuses.mwak.damage", label: "SW5E.BonusMWDamage"},
|
||||
{name: "data.bonuses.rwak.attack", label: "SW5E.BonusRWAttack"},
|
||||
{name: "data.bonuses.rwak.damage", label: "SW5E.BonusRWDamage"},
|
||||
{name: "data.bonuses.mpak.attack", label: "SW5E.BonusMPAttack"},
|
||||
{name: "data.bonuses.mpak.damage", label: "SW5E.BonusMPDamage"},
|
||||
{name: "data.bonuses.rpak.attack", label: "SW5E.BonusRPAttack"},
|
||||
{name: "data.bonuses.rpak.damage", label: "SW5E.BonusRPDamage"},
|
||||
{name: "data.bonuses.msak.attack", label: "SW5E.BonusMSAttack"},
|
||||
{name: "data.bonuses.msak.damage", label: "SW5E.BonusMSDamage"},
|
||||
{name: "data.bonuses.rsak.attack", label: "SW5E.BonusRSAttack"},
|
||||
{name: "data.bonuses.rsak.damage", label: "SW5E.BonusRSDamage"},
|
||||
{name: "data.bonuses.abilities.check", label: "SW5E.BonusAbilityCheck"},
|
||||
{name: "data.bonuses.abilities.save", label: "SW5E.BonusAbilitySave"},
|
||||
{name: "data.bonuses.abilities.skill", label: "SW5E.BonusAbilitySkill"},
|
||||
{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);
|
||||
|
@ -103,7 +95,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
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 +110,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});
|
||||
}
|
||||
}
|
||||
|
|
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.rolls[0]) || d.options.marginSuccess || d.options.marginFailure;
|
||||
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);
|
||||
|
|
|
@ -1,35 +1,691 @@
|
|||
export const ClassFeatures = {
|
||||
"berserker": {
|
||||
"archetypes": {
|
||||
"addicted-approach": {
|
||||
"label": "Addicted Approach",
|
||||
"source": "PHB",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.archetypes.PCwepUZqHYlxr4T3", "Compendium.sw5e.classfeatures.efOA0nrvUqKJOOeP", "Compendium.sw5e.classfeatures.nT6AfpQXSZ4IeChO"],
|
||||
"6": ["Compendium.sw5e.classfeatures.GbJDWzoTKWL7sEpR"],
|
||||
"10": ["Compendium.sw5e.classfeatures.3jqPPd5qJBBnonPw"],
|
||||
"14": ["Compendium.sw5e.classfeatures.xzRNHB2M2HdOZzr7"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.IDt6duVrBzL8euRc", "Compendium.sw5e.classfeatures.rPOLy96fW96N2UPg"],
|
||||
"2": ["Compendium.sw5e.classfeatures.DlYiCiG39R0goG9u", "Compendium.sw5e.classfeatures.FbSpxpXm1xONn0na", "Compendium.sw5e.classfeatures.KDiQ8O2evV2Z1YTo", "Compendium.sw5e.classfeatures.Q1JyHnVs9iIEBs91", "Compendium.sw5e.classfeatures.ROdICoWR82v6A2Rf", "Compendium.sw5e.classfeatures.cdCx5Hvq2rYRMzRj", "Compendium.sw5e.classfeatures.dTdbL8dypa6BAdnP", "Compendium.sw5e.classfeatures.h1uDhP1tEOuvjRw6", "Compendium.sw5e.classfeatures.hMiA075EKBBOL2cv", "Compendium.sw5e.classfeatures.sgJdISZMtwv08WPJ", "Compendium.sw5e.classfeatures.v4CZJ8LBMl5PYZCO"],
|
||||
"3": ["Compendium.sw5e.classfeatures.kzwSN9SabKgWZZvU"],
|
||||
"4": ["Compendium.sw5e.classfeatures.9oyy0MMqEws2qoil"],
|
||||
"5": ["Compendium.sw5e.classfeatures.dPWmHiWmpnhHTsgd"],
|
||||
"7": ["Compendium.sw5e.classfeatures.Cid5ujSdnooH0vMm", "Compendium.sw5e.classfeatures.WTBhKJgkArQI3Tgv", "Compendium.sw5e.classfeatures.oiT3TJxzRWPKAX9E", "Compendium.sw5e.classfeatures.pMEmIt3NWThbee8k", "Compendium.sw5e.classfeatures.qWV5YogZcpZ3Y3xj"],
|
||||
"9": ["Compendium.sw5e.classfeatures.bi8G8H5Ur9B3BAyM"],
|
||||
"11": ["Compendium.sw5e.classfeatures.eWbTifdXJvvXT4CV"],
|
||||
"13": ["Compendium.sw5e.classfeatures.Hg8zYh1iXL0DGUVq", "Compendium.sw5e.classfeatures.QRnYiJmRk18ekE9v", "Compendium.sw5e.classfeatures.sfEr8ZBFVddlfLeF", "Compendium.sw5e.classfeatures.yGC9VzT840qQWxca"],
|
||||
"15": ["Compendium.sw5e.classfeatures.YHPUv9lN3nCapAgP"],
|
||||
"18": ["Compendium.sw5e.classfeatures.fFKNqUAWh0ZOhvRc"],
|
||||
"20": ["Compendium.sw5e.classfeatures.IWTDawTUf79eWbEV"]
|
||||
}
|
||||
},
|
||||
"consular": {
|
||||
"features": {
|
||||
"20": ["Compendium.sw5e.classfeatures.gSGeitc98ItAwhfF"]
|
||||
}
|
||||
}
|
||||
};
|
||||
"barbarian": {
|
||||
"archetypes": {
|
||||
"path-of-the-ancestral-guardian": {
|
||||
"label": "Path of the Ancestral Guardian",
|
||||
"source": "XGE pg. 9"
|
||||
},
|
||||
"path-of-the-battlerager": {
|
||||
"label": "Path of the Battlerager",
|
||||
"source": "SCAG pg. 121"
|
||||
},
|
||||
"path-of-the-berserker": {
|
||||
"label": "Path of the Berserker",
|
||||
"source": "PHB pg. 49",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.classfeatures.CkbbAckeCtyHXEnL"],
|
||||
"6": ["Compendium.sw5e.classfeatures.0Jgf8fYY2ExwgQpN"],
|
||||
"10": ["Compendium.sw5e.classfeatures.M6VSMzVtKPhh8B0i"],
|
||||
"14": ["Compendium.sw5e.classfeatures.xzD9zlRP6dUxCtCl"]
|
||||
}
|
||||
},
|
||||
"path-of-the-juggernaut": {
|
||||
"label": "Path of the Juggernaut",
|
||||
"source": "TCS pg. 102"
|
||||
},
|
||||
"path-of-the-storm-herald": {
|
||||
"label": "Path of the Storm Herald",
|
||||
"source": "XGE pg. 10"
|
||||
},
|
||||
"path-of-the-totem-warrior": {
|
||||
"label": "Path of the Totem Warrior",
|
||||
"source": "PHB pg. 50; SCAG pg. 121"
|
||||
},
|
||||
"path-of-the-zealot": {
|
||||
"label": "Path of the Zealot",
|
||||
"source": "XGE pg. 11"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.VoR0SUrNX5EJVPIO", "Compendium.sw5e.classfeatures.SZbsNbaxFFGwBpNK"],
|
||||
"2": ["Compendium.sw5e.classfeatures.SCVjqRdlZ9cvHVSR", "Compendium.sw5e.classfeatures.vt31lWAULygEl7yk"],
|
||||
"3": ["Compendium.sw5e.classfeatures.TH1QAf6YNGSeBVjT"],
|
||||
"5": ["Compendium.sw5e.classfeatures.XogoBnFWmCAHXppo", "Compendium.sw5e.classfeatures.Kl6zifJ5OmdHlOi2"],
|
||||
"7": ["Compendium.sw5e.classfeatures.NlXslw4yAqmKZWtN"],
|
||||
"9": ["Compendium.sw5e.classfeatures.L94gyvNpUhUe0rwh"],
|
||||
"11": ["Compendium.sw5e.classfeatures.FqfmbPgxiyrWzhYk"],
|
||||
"15": ["Compendium.sw5e.classfeatures.l8tUhZ5Pecm9wz7I"],
|
||||
"18": ["Compendium.sw5e.classfeatures.Q1exex5ALteprrPo"],
|
||||
"20": ["Compendium.sw5e.classfeatures.jVU4AgqfrFaqgXns"]
|
||||
}
|
||||
},
|
||||
"bard": {
|
||||
"archetypes": {
|
||||
"college-of-glamour": {
|
||||
"label": "College of Glamour",
|
||||
"source": "XGE pg. 14",
|
||||
"features": {}
|
||||
},
|
||||
"college-of-lore": {
|
||||
"label": "College of Lore",
|
||||
"source": "PHB pg. 54",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.classfeatures.5zPmHPQUne7RDfaU"],
|
||||
"6": ["Compendium.sw5e.classfeatures.myBu3zi5eYvQIcuy"],
|
||||
"14": ["Compendium.sw5e.classfeatures.pquwueEMweRhiWaq"]
|
||||
}
|
||||
},
|
||||
"college-of-swords": {
|
||||
"label": "College of Swords",
|
||||
"source": "XGE pg. 15",
|
||||
"features": {}
|
||||
},
|
||||
"college-of-valor": {
|
||||
"label": "College of Valor",
|
||||
"source": "PHB pg. 55",
|
||||
"features": {}
|
||||
},
|
||||
"college-of-whispers": {
|
||||
"label": "College of Whispers",
|
||||
"source": "XGE pg. 16",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.hpLNiGq7y67d2EHA", "Compendium.sw5e.classfeatures.u4NLajXETJhJU31v"],
|
||||
"2": ["Compendium.sw5e.classfeatures.ezWijmCnlnQ9ZRX2", "Compendium.sw5e.classfeatures.he8RpPXwSl2lVSIk"],
|
||||
"3": ["Compendium.sw5e.classfeatures.ILhzFHiRrqgQ9dFJ", "Compendium.sw5e.classfeatures.aQLg7BWdRnm4Hr9S"],
|
||||
"5": ["Compendium.sw5e.classfeatures.3VDZGs5Ug3hIE322"],
|
||||
"6": ["Compendium.sw5e.classfeatures.SEJmsjkEhdAZ90ki"],
|
||||
"10": ["Compendium.sw5e.classfeatures.aonJ2YjkqkYB9WYB"],
|
||||
"20": ["Compendium.sw5e.classfeatures.GBYN5rH4nh1ocRlY"]
|
||||
}
|
||||
},
|
||||
"cleric": {
|
||||
"archetypes": {
|
||||
"ambition-domain": {
|
||||
"label": "Ambition Domain",
|
||||
"source": "PS:A pg. 27",
|
||||
"features": {}
|
||||
},
|
||||
"arcana-domain": {
|
||||
"label": "Arcana Domain",
|
||||
"source": "SCAG pg. 125",
|
||||
"features": {}
|
||||
},
|
||||
"blood-domain": {
|
||||
"label": "Blood Domain",
|
||||
"source": "TCS pg. 101",
|
||||
"features": {}
|
||||
},
|
||||
"death-domain": {
|
||||
"label": "Death Domain",
|
||||
"source": "DMG pg. 96",
|
||||
"features": {}
|
||||
},
|
||||
"forge-domain": {
|
||||
"label": "Forge Domain",
|
||||
"source": "XGE pg. 18",
|
||||
"features": {}
|
||||
},
|
||||
"grave-domain": {
|
||||
"label": "Grave Domain",
|
||||
"source": "XGE pg. 19",
|
||||
"features": {}
|
||||
},
|
||||
"knowledge-domain": {
|
||||
"label": "Knowledge Domain",
|
||||
"source": "PHB pg. 59",
|
||||
"features": {}
|
||||
},
|
||||
"life-domain": {
|
||||
"label": "Life Domain",
|
||||
"source": "PHB pg. 60",
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.68bYIOvx6rIqnlOW", "Compendium.sw5e.classfeatures.jF8AFfEMICIJnAkR", "Compendium.sw5e.powers.8dzaICjGy6mTUaUr", "Compendium.sw5e.powers.uUWb1wZgtMou0TVP"],
|
||||
"2": ["Compendium.sw5e.classfeatures.hEymt45rICi4f9eL"],
|
||||
"3": ["Compendium.sw5e.powers.F0GsG0SJzsIOacwV", "Compendium.sw5e.powers.JbxsYXxSOTZbf9I0"],
|
||||
"5": ["Compendium.sw5e.powers.ZU9d6woBdUP8pIPt", "Compendium.sw5e.powers.LmRHHMtplpxr9fX6"],
|
||||
"6": ["Compendium.sw5e.classfeatures.yv49QN6Bzqs0ecCs"],
|
||||
"7": ["Compendium.sw5e.powers.VtCXMdyM6mAdIJZb", "Compendium.sw5e.powers.TgHsuhNasPbhu8MO"],
|
||||
"8": ["Compendium.sw5e.classfeatures.T6u5z8ZTX6UftXqE"],
|
||||
"9": ["Compendium.sw5e.powers.Pyzmm8R7rVsNAPsd", "Compendium.sw5e.powers.AGFMPAmuzwWO6Dfz"],
|
||||
"17": ["Compendium.sw5e.classfeatures.4UOgxzr83vFuUash"]
|
||||
}
|
||||
},
|
||||
"light-domain": {
|
||||
"label": "Light Domain",
|
||||
"source": "PHB pg. 60",
|
||||
"features": {}
|
||||
},
|
||||
"nature-domain": {
|
||||
"label": "Nature Domain",
|
||||
"source": "PHB pg. 61",
|
||||
"features": {}
|
||||
},
|
||||
"order-domain": {
|
||||
"label": "Order Domain",
|
||||
"source": "GGR pg. 25",
|
||||
"features": {}
|
||||
},
|
||||
"solidarity-domain": {
|
||||
"label": "Solidarity Domain",
|
||||
"source": "PS:A pg. 24",
|
||||
"features": {}
|
||||
},
|
||||
"strength-domain": {
|
||||
"label": "Strength Domain",
|
||||
"source": "PS:A pg. 25",
|
||||
"features": {}
|
||||
},
|
||||
"tempest-domain": {
|
||||
"label": "Tempest Domain",
|
||||
"source": "PHB pg. 62",
|
||||
"features": {}
|
||||
},
|
||||
"trickery-domain": {
|
||||
"label": "Trickery Domain",
|
||||
"source": "PHB pg. 62",
|
||||
"features": {}
|
||||
},
|
||||
"war-domain": {
|
||||
"label": "War Domain",
|
||||
"source": "PHB pg. 63",
|
||||
"features": {}
|
||||
},
|
||||
"zeal-domain": {
|
||||
"label": "Zeal Domain",
|
||||
"source": "PS:A pg. 28",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.x637K2Icp2ZFM1TB", "Compendium.sw5e.classfeatures.v4gKwLhAq9vuqza7"],
|
||||
"2": ["Compendium.sw5e.classfeatures.YpiLQEKGalROn7iJ"],
|
||||
"5": ["Compendium.sw5e.classfeatures.NMy4piwXIpLjYbRE"],
|
||||
"10": ["Compendium.sw5e.classfeatures.eVXqHn0ojWrEuYGU"]
|
||||
},
|
||||
},
|
||||
"druid": {
|
||||
"archetypes": {
|
||||
"circle-of-dreams": {
|
||||
"label": "Circle of Dreams",
|
||||
"source": "XGE pg. 22",
|
||||
"features": {}
|
||||
},
|
||||
"circle-of-the-land": {
|
||||
"label": "Circle of the Land",
|
||||
"source": "PHB pg. 68",
|
||||
"features": {
|
||||
"2": ["Compendium.sw5e.classfeatures.lT8GsPOPgRzDC3QJ", "Compendium.sw5e.classfeatures.wKdRtFsvGfMKQHLY"],
|
||||
"3": ["Compendium.sw5e.classfeatures.YiK59gWSlcQ6Mbdz"],
|
||||
"6": ["Compendium.sw5e.classfeatures.3FB25qKxmkmxcxuC"],
|
||||
"10": ["Compendium.sw5e.classfeatures.OTvrJSJSUgAwXrWX"],
|
||||
"14": ["Compendium.sw5e.classfeatures.EuX1kJNIw1F68yus"]
|
||||
}
|
||||
},
|
||||
"circle-of-the-moon": {
|
||||
"label": "Circle of the Moon",
|
||||
"source": "PHB pg. 69",
|
||||
"features": {}
|
||||
},
|
||||
"circle-of-the-shepherd": {
|
||||
"label": "Circle of the Shepherd",
|
||||
"source": "XGE pg. 23",
|
||||
"features": {}
|
||||
},
|
||||
"circle-of-spores": {
|
||||
"label": "Circle of Spores",
|
||||
"source": "GGR pg. 26",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.LzJ5ayHt0OlSVGxi", "Compendium.sw5e.classfeatures.i6tPm3FNK13Ftc9v"],
|
||||
"2": ["Compendium.sw5e.classfeatures.swK0r5TOIxredxWS", "Compendium.sw5e.classfeatures.u6Du2P9s81SWuGbi"],
|
||||
"18": ["Compendium.sw5e.classfeatures.cVDEQo0ow1WJT7Wl", "Compendium.sw5e.classfeatures.xvgPu1O57DgXCM86"],
|
||||
"20": ["Compendium.sw5e.classfeatures.ip4bvmGoz3qkoqes"]
|
||||
},
|
||||
},
|
||||
"fighter": {
|
||||
"archetypes": {
|
||||
"arcane-archer": {
|
||||
"label": "Arcane Archer",
|
||||
"source": "XGE pg. 28",
|
||||
"features": {}
|
||||
},
|
||||
"banneret": {
|
||||
"label": "Banneret",
|
||||
"source": "SCAG pg. 128",
|
||||
"features": {}
|
||||
},
|
||||
"battle-master": {
|
||||
"label": "Battle Master",
|
||||
"source": "PHB pg. 73",
|
||||
"features": {}
|
||||
},
|
||||
"cavalier": {
|
||||
"label": "Cavalier",
|
||||
"source": "XGE pg. 30",
|
||||
"features": {}
|
||||
},
|
||||
"champion": {
|
||||
"label": "Champion",
|
||||
"source": "PHB pg. 72",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.classfeatures.YgLQV1O849wE5TgM"],
|
||||
"7": ["Compendium.sw5e.classfeatures.dHu1yzIjD38BvGGd"],
|
||||
"11": ["Compendium.sw5e.classfeatures.kYJsED0rqqqUcgKz"],
|
||||
"15": ["Compendium.sw5e.classfeatures.aVKH6TLn1AG9hPSA"],
|
||||
"18": ["Compendium.sw5e.classfeatures.ipG5yx1tRNmeJfSH"]
|
||||
}
|
||||
},
|
||||
"echo-knight": {
|
||||
"label": "Echo Knight",
|
||||
"source": "EGW pg. 183",
|
||||
"features": {}
|
||||
},
|
||||
"eldritch-knight": {
|
||||
"label": "Eldritch Knight",
|
||||
"source": "PHB pg. 74",
|
||||
"features": {}
|
||||
},
|
||||
"samurai": {
|
||||
"label": "Samurai",
|
||||
"source": "XGE pg. 31",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.fbExzwNwEAl2kW9c", "Compendium.sw5e.classfeatures.nTjmWbyHweXuIqwc"],
|
||||
"2": ["Compendium.sw5e.classfeatures.xF1VTcJ3AdkbTsdQ"],
|
||||
"3": ["Compendium.sw5e.classfeatures.ax8M0X0q1GGWM26j"],
|
||||
"5": ["Compendium.sw5e.classfeatures.q9g1MLXuLZyxjQMg"],
|
||||
"9": ["Compendium.sw5e.classfeatures.653ZHbNcmm7ZGXbw"]
|
||||
},
|
||||
},
|
||||
"monk": {
|
||||
"archetypes": {
|
||||
"way-of-the-cobalt-soul": {
|
||||
"label": "Way of the Cobalt Soul",
|
||||
"source": "TCS pg. 104",
|
||||
"features": {}
|
||||
},
|
||||
"way-of-the-drunken-master": {
|
||||
"label": "Way of the Drunken Master",
|
||||
"source": "XGE pg. 33",
|
||||
"features": {}
|
||||
},
|
||||
"way-of-the-elements": {
|
||||
"label": "Way of the Four Elements",
|
||||
"source": "PHB pg. 80",
|
||||
"features": {}
|
||||
},
|
||||
"way-of-the-kensei": {
|
||||
"label": "Way of the Kensei",
|
||||
"source": "XGE pg. 34",
|
||||
"features": {}
|
||||
},
|
||||
"way-of-the-long-death": {
|
||||
"label": "Way of the Long Death",
|
||||
"source": "SCAG pg. 130",
|
||||
"features": {}
|
||||
},
|
||||
"way-of-the-open-hand": {
|
||||
"label": "Way of the Open Hand",
|
||||
"source": "PHB pg. 79",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.classfeatures.iQxLNydNLlCHNKbp"],
|
||||
"6": ["Compendium.sw5e.classfeatures.Q7mOdk4b1lgjcptF"],
|
||||
"11": ["Compendium.sw5e.classfeatures.rBDZLatuoolT2FUW"],
|
||||
"17": ["Compendium.sw5e.classfeatures.h1gM8SH3BNRtFevE"]
|
||||
}
|
||||
},
|
||||
"way-of-the-shadow": {
|
||||
"label": "Way of Shadow",
|
||||
"source": "PHB pg. 80",
|
||||
"features": {}
|
||||
},
|
||||
"way-of-the-sun-soul": {
|
||||
"label": "Way of the Sun Soul",
|
||||
"source": "XGE pg. 35; SCAG pg. 131",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.UAvV7N7T4zJhxdfI", "Compendium.sw5e.classfeatures.l50hjTxO2r0iecKw"],
|
||||
"2": ["Compendium.sw5e.classfeatures.10b6z2W1txNkrGP7", "Compendium.sw5e.classfeatures.7vSrGc0MP5Vzm9Ac"],
|
||||
"3": ["Compendium.sw5e.classfeatures.rtpQdX77dYWbDIOH", "Compendium.sw5e.classfeatures.mzweVbnsJPQiVkAe"],
|
||||
"4": ["Compendium.sw5e.classfeatures.KQz9bqxVkXjDl8gK"],
|
||||
"5": ["Compendium.sw5e.classfeatures.XogoBnFWmCAHXppo", "Compendium.sw5e.classfeatures.pvRc6GAu1ok6zihC"],
|
||||
"6": ["Compendium.sw5e.classfeatures.7flZKruSSu6dHg6D"],
|
||||
"7": ["Compendium.sw5e.classfeatures.a4P4DNMmH8CqSNkC", "Compendium.sw5e.classfeatures.ZmC31XKS4YNENnoc"],
|
||||
"10": ["Compendium.sw5e.classfeatures.bqWA7t9pDELbNRkp"],
|
||||
"13": ["Compendium.sw5e.classfeatures.XjuGBeB8Y0C3A5D4"],
|
||||
"14": ["Compendium.sw5e.classfeatures.7D2EkLdISwShEDlN"],
|
||||
"15": ["Compendium.sw5e.classfeatures.gDH8PMrKvLHaNmEI"],
|
||||
"18": ["Compendium.sw5e.classfeatures.3jwFt3hSqDswBlOH"],
|
||||
"20": ["Compendium.sw5e.classfeatures.mQNPg89YIs7g5tG4"]
|
||||
},
|
||||
},
|
||||
"paladin": {
|
||||
"archetypes": {
|
||||
"oath-of-the-ancients": {
|
||||
"label": "Oath of the Ancients",
|
||||
"source": "PHB pg. 86",
|
||||
"features": {}
|
||||
},
|
||||
"oath-of-conquest": {
|
||||
"label": "Oath of Conquest",
|
||||
"source": "SCAG pg. 128",
|
||||
"features": {}
|
||||
},
|
||||
"oath-of-the-crown": {
|
||||
"label": "Oath of the Crown",
|
||||
"source": "SCAG pg. 132",
|
||||
"features": {}
|
||||
},
|
||||
"oath-of-devotion": {
|
||||
"label": "Oath of Devotion",
|
||||
"source": "PHB pg. 85",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.powers.xmDBqZhRVrtLP8h2", "Compendium.sw5e.powers.gvdA9nPuWLck4tBl"],
|
||||
"5": ["Compendium.sw5e.powers.F0GsG0SJzsIOacwV", "Compendium.sw5e.powers.CylBa7jR8DSbo8Z3"],
|
||||
"9": ["Compendium.sw5e.powers.ZU9d6woBdUP8pIPt", "Compendium.sw5e.powers.15Fa6q1nH27XfbR8"],
|
||||
"13": ["Compendium.sw5e.powers.da0a1t2FqaTjRZGT", "Compendium.sw5e.powers.TgHsuhNasPbhu8MO"],
|
||||
"17": ["Compendium.sw5e.powers.d54VDyFulD9xxY7J", "Compendium.sw5e.powers.5e1xTohkzqFqbYH4"]
|
||||
}
|
||||
},
|
||||
"oathbreaker": {
|
||||
"label": "Oathbreaker",
|
||||
"source": "DMG pg. 97",
|
||||
"features": {}
|
||||
},
|
||||
"oath-of-redemption": {
|
||||
"label": "Oath of Redemption",
|
||||
"source": "XGE pg. 38",
|
||||
"features": {}
|
||||
},
|
||||
"oath-of-vengeance": {
|
||||
"label": "Oath of Vengeance",
|
||||
"source": "PHB pg. 87",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.E8ozg8avUVOX9N7u", "Compendium.sw5e.classfeatures.OdrvL3afwLOPeuYZ"],
|
||||
"2": ["Compendium.sw5e.classfeatures.ySMPQ6zNSlvkrl2f", "Compendium.sw5e.classfeatures.fbExzwNwEAl2kW9c", "Compendium.sw5e.classfeatures.ihoQHsmVZlyDbPhX"],
|
||||
"3": ["Compendium.sw5e.classfeatures.dY9yrqkyEDuF0CG2", "Compendium.sw5e.classfeatures.olAqNsUTIef9x8xC"],
|
||||
"5": ["Compendium.sw5e.classfeatures.XogoBnFWmCAHXppo"],
|
||||
"6": ["Compendium.sw5e.classfeatures.carGDhkIdoduTC0I"],
|
||||
"10": ["Compendium.sw5e.classfeatures.nahSkBO6LH4HkpaT"],
|
||||
"11": ["Compendium.sw5e.classfeatures.FAk41RPCTcvCk6KI"],
|
||||
"14": ["Compendium.sw5e.classfeatures.U7BIPVPsptBmwsnV"]
|
||||
},
|
||||
},
|
||||
"ranger": {
|
||||
"archetypes": {
|
||||
"beast-master": {
|
||||
"label": "Beast Master",
|
||||
"source": "PHB pg. 93",
|
||||
"features": {}
|
||||
},
|
||||
"gloom-stalker": {
|
||||
"label": "Gloom Stalker",
|
||||
"source": "XGE pg. 41",
|
||||
"features": {}
|
||||
},
|
||||
"horizon-walker": {
|
||||
"label": "Horizon Walker",
|
||||
"source": "XGE pg. 42",
|
||||
"features": {}
|
||||
},
|
||||
"hunter": {
|
||||
"label": "Hunter",
|
||||
"source": "PHB pg. 93",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.classfeatures.wrxIW5sDfmGr3u5s"],
|
||||
"7": ["Compendium.sw5e.classfeatures.WgQrqjmeyMqDzVt3"],
|
||||
"11": ["Compendium.sw5e.classfeatures.7zlTRRXT1vWSBGjX"],
|
||||
"15": ["Compendium.sw5e.classfeatures.a0Sq88dgnREcIMfl"]
|
||||
}
|
||||
},
|
||||
"monster-slayer": {
|
||||
"label": "Monster Slayer",
|
||||
"source": "XGE pg. 43",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.4Vpj9vCOB37GtXk6", "Compendium.sw5e.classfeatures.8fbZt2Qh7ZttwIan"],
|
||||
"2": ["Compendium.sw5e.classfeatures.fbExzwNwEAl2kW9c", "Compendium.sw5e.classfeatures.u6xV3Ki3TXRrD7zg"],
|
||||
"3": ["Compendium.sw5e.classfeatures.1dJHU48yNqn3lcfx", "Compendium.sw5e.classfeatures.kaHcUGiwi8AtfZIm"],
|
||||
"5": ["Compendium.sw5e.classfeatures.XogoBnFWmCAHXppo"],
|
||||
"8": ["Compendium.sw5e.classfeatures.C5fzaOBc6HxyOWRn"],
|
||||
"10": ["Compendium.sw5e.classfeatures.r0unvWK0lPsDthDx"],
|
||||
"14": ["Compendium.sw5e.classfeatures.DhU2dWCNnX78TstR"],
|
||||
"18": ["Compendium.sw5e.classfeatures.QBVmY56RMQuh6C8h"],
|
||||
"20": ["Compendium.sw5e.classfeatures.3CaP1vFHVR8LgHjx"]
|
||||
},
|
||||
},
|
||||
"rogue": {
|
||||
"archetypes": {
|
||||
"arcane-trickster": {
|
||||
"label": "Arcane Trickster",
|
||||
"source": "PHB pg. 97",
|
||||
"features": {}
|
||||
},
|
||||
"assassin": {
|
||||
"label": "Assassin",
|
||||
"source": "PHB pg. 97",
|
||||
"features": {}
|
||||
},
|
||||
"inquisitive": {
|
||||
"label": "Inquisitive",
|
||||
"source": "XGE pg. 45",
|
||||
"features": {}
|
||||
},
|
||||
"mastermind": {
|
||||
"label": "Mastermind",
|
||||
"source": "XGE pg. 46; SCAG pg. 135",
|
||||
"features": {}
|
||||
},
|
||||
"scout": {
|
||||
"label": "Scout",
|
||||
"source": "XGE pg. 47",
|
||||
"features": {}
|
||||
},
|
||||
"swashbuckler": {
|
||||
"label": "Swashbuckler",
|
||||
"source": "XGE pg. 47; SCAG pg. 135",
|
||||
"features": {}
|
||||
},
|
||||
"thief": {
|
||||
"label": "Thief",
|
||||
"source": "PHB pg. 97",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.classfeatures.ga3dt2zrCn2MHK8R", "Compendium.sw5e.classfeatures.FGrbXs6Ku5qxFK5G"],
|
||||
"9": ["Compendium.sw5e.classfeatures.Ei1Oh4UAA2E30jcD"],
|
||||
"13": ["Compendium.sw5e.classfeatures.NqWyHE7Rpw9lyKWu"],
|
||||
"17": ["Compendium.sw5e.classfeatures.LhRm1EeUMvp2EWhV"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.3sYPftQKnbbVnHrh", "Compendium.sw5e.classfeatures.DPN2Gfk8yi1Z5wp7", "Compendium.sw5e.classfeatures.ohwfuwnvuoBWlSQr"],
|
||||
"2": ["Compendium.sw5e.classfeatures.01pcLg6PRu5zGrsb"],
|
||||
"3": ["Compendium.sw5e.classfeatures.80USV8ZFPIahpLd0"],
|
||||
"5": ["Compendium.sw5e.classfeatures.Mm64SKAHJWYecgXS"],
|
||||
"7": ["Compendium.sw5e.classfeatures.a4P4DNMmH8CqSNkC"],
|
||||
"11": ["Compendium.sw5e.classfeatures.YN9xm6MCvse4Y60u"],
|
||||
"14": ["Compendium.sw5e.classfeatures.fjsBk7zxoAbLf8ZI"],
|
||||
"15": ["Compendium.sw5e.classfeatures.V4pwFxlwHtNeB4w9"],
|
||||
"18": ["Compendium.sw5e.classfeatures.L7nJSRosos8sHJH9"],
|
||||
"20": ["Compendium.sw5e.classfeatures.rQhWDaMHMn7iU4f2"]
|
||||
},
|
||||
},
|
||||
"sorcerer": {
|
||||
"archetypes": {
|
||||
"draconic-bloodline": {
|
||||
"label": "Draconic Bloodline",
|
||||
"source": "PHB pg. 102",
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.EZsonMThTNLZq35j", "Compendium.sw5e.classfeatures.MW1ExvBLm8Hg82aA"],
|
||||
"6": ["Compendium.sw5e.classfeatures.x6eEZ9GUsuOcEa3G"],
|
||||
"14": ["Compendium.sw5e.classfeatures.3647zjKSE9zFwOXc"],
|
||||
"18": ["Compendium.sw5e.classfeatures.Gsha4bl0apxqspFy"]
|
||||
}
|
||||
},
|
||||
"divine-soul": {
|
||||
"label": "Divine Soul",
|
||||
"source": "XGE pg. 50",
|
||||
"features": {}
|
||||
},
|
||||
"pyromancer": {
|
||||
"label": "Pyromancer",
|
||||
"source": "PS:K pg. 9",
|
||||
"features": {}
|
||||
},
|
||||
"runechild": {
|
||||
"label": "Runechild",
|
||||
"source": "TCS pg. 103",
|
||||
"features": {}
|
||||
},
|
||||
"shadow-magic": {
|
||||
"label": "Shadow Magic",
|
||||
"source": "XGE pg. 50",
|
||||
"features": {}
|
||||
},
|
||||
"storm-sorcery": {
|
||||
"label": "Storm Sorcery",
|
||||
"source": "XGE pg. 51; SCAG pg. 137",
|
||||
"features": {}
|
||||
},
|
||||
"wild-magic": {
|
||||
"label": "Wild Magic",
|
||||
"source": "PHB pg. 103",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.cmRCL9T9UgRYOj1c", "Compendium.sw5e.classfeatures.oygRF3ZjTv2T7z0Y"],
|
||||
"2": ["Compendium.sw5e.classfeatures.LBKChJY5n02Afhnq"],
|
||||
"3": ["Compendium.sw5e.classfeatures.9Uh7uTDNZ04oTJsL"],
|
||||
"20": ["Compendium.sw5e.classfeatures.F2lEKSmOY0NUruzY"]
|
||||
},
|
||||
},
|
||||
"warlock": {
|
||||
"archetypes": {
|
||||
"the-archfey": {
|
||||
"label": "The Archfey",
|
||||
"source": "PHB pg. 108",
|
||||
"features": {}
|
||||
},
|
||||
"the-celestial": {
|
||||
"label": "The Celestial",
|
||||
"source": "XGE pg. 54",
|
||||
"features": {}
|
||||
},
|
||||
"the-fiend": {
|
||||
"label": "The Fiend",
|
||||
"source": "PHB pg. 109",
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.Jv0zu4BtUi8bFCqJ"],
|
||||
"6": ["Compendium.sw5e.classfeatures.OQSb0bO1yDI4aiMx"],
|
||||
"10": ["Compendium.sw5e.classfeatures.9UZ2WjUF2k58CQug"],
|
||||
"14": ["Compendium.sw5e.classfeatures.aCUmlnHlUPHS0rdu"]
|
||||
}
|
||||
},
|
||||
"the-hexblade": {
|
||||
"label": "The Hexblade",
|
||||
"source": "XGE pg. 55",
|
||||
"features": {}
|
||||
},
|
||||
"the-oldone": {
|
||||
"label": "The Great Old One",
|
||||
"source": "PHB pg. 109",
|
||||
"features": {}
|
||||
},
|
||||
"the-undying": {
|
||||
"label": "The Undying",
|
||||
"source": "SCAG pg. 139",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.jTXHaK0vvT5DV3uO", "Compendium.sw5e.classfeatures.x6IJZwr6f0SGral7"],
|
||||
"2": ["Compendium.sw5e.classfeatures.8MlxM2nEfE3Q0EVk"],
|
||||
"3": ["Compendium.sw5e.classfeatures.QwgfIpCN8VWfoUtX"],
|
||||
"11": ["Compendium.sw5e.classfeatures.zB77V8BcCJvWVxck"],
|
||||
"13": ["Compendium.sw5e.classfeatures.HBn6FXLz7Eiudz0V"],
|
||||
"15": ["Compendium.sw5e.classfeatures.knDZR4l4QfLTKinm"],
|
||||
"17": ["Compendium.sw5e.classfeatures.vMxJQEKeK6WwZFaF"],
|
||||
"20": ["Compendium.sw5e.classfeatures.0C04rwyvoknvFYiy"]
|
||||
},
|
||||
},
|
||||
"wizard": {
|
||||
"archetypes": {
|
||||
"school-of-abjuration": {
|
||||
"label": "School of Abjuration",
|
||||
"source": "PHB pg. 115",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-bladesinging": {
|
||||
"label": "School of Bladesinging",
|
||||
"source": "SCAG pg. 141",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-chronurgy-magic": {
|
||||
"label": "School of Chronurgy Magic",
|
||||
"source": "EGW pg. 185",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-conjuration": {
|
||||
"label": "School of Conjuration",
|
||||
"source": "PHB pg. 116",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-divination": {
|
||||
"label": "School of Divination",
|
||||
"source": "PHB pg. 116",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-enchantment": {
|
||||
"label": "School of Enchantment",
|
||||
"source": "PHB pg. 117",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-evocation": {
|
||||
"label": "School of Evocation",
|
||||
"source": "PHB pg. 117",
|
||||
"features": {
|
||||
"2": ["Compendium.sw5e.classfeatures.7uzJ2JkmsdRGLra3", "Compendium.sw5e.classfeatures.6VBXkjjBgjSpNElh"],
|
||||
"6": ["Compendium.sw5e.classfeatures.evEWCpE5MYgr5RRW"],
|
||||
"10": ["Compendium.sw5e.classfeatures.7O85kj6uDEG5NzUE"],
|
||||
"14": ["Compendium.sw5e.classfeatures.VUtSLeCzFubGXmGx"]
|
||||
}
|
||||
},
|
||||
"school-of-graviturgy-magic": {
|
||||
"label": "School of Graviturgy Magic",
|
||||
"source": "EGW pg. 185",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-illusion": {
|
||||
"label": "School of Illusion",
|
||||
"source": "PHB pg. 118",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-necromancy": {
|
||||
"label": "School of Necromancy",
|
||||
"source": "PHB pg. 118",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-transmutation": {
|
||||
"label": "School of Transmutation",
|
||||
"source": "PHB pg. 119",
|
||||
"features": {}
|
||||
},
|
||||
"school-of-war-magic": {
|
||||
"label": "School of War Magic",
|
||||
"source": "XGE pg. 59",
|
||||
"features": {}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.gbNo5eVPaqr8IVKL", "Compendium.sw5e.classfeatures.e0uTcFPpgxjIyUW9"],
|
||||
"2": ["Compendium.sw5e.classfeatures.AEWr9EMxy5gj4ZFT"],
|
||||
"18": ["Compendium.sw5e.classfeatures.JfFfHTeIszx1hNRx"],
|
||||
"20": ["Compendium.sw5e.classfeatures.nUrZDi6QN1YjwAr6", "Compendium.sw5e.classfeatures.31bKbWe9ZGVLEns6"]
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
/**
|
||||
* Override the default Initiative formula to customize special behaviors of the SW5e system.
|
||||
* Override the default Initiative formula to customize special behaviors of the system.
|
||||
* Apply advantage, proficiency, or bonuses where appropriate
|
||||
* Apply the dexterity score as a decimal tiebreaker if requested
|
||||
* See Combat._getInitiativeFormula for more detail.
|
||||
|
@ -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;
|
||||
};
|
||||
});
|
||||
|
|
490
module/config.js
490
module/config.js
|
@ -1,17 +1,17 @@
|
|||
import {ClassFeatures} from "./classFeatures.js"
|
||||
|
||||
// Namespace SW5e Configuration Values
|
||||
// Namespace Configuration Values
|
||||
export const SW5E = {};
|
||||
|
||||
// ASCII Artwork
|
||||
SW5E.ASCII = `__________________________________________
|
||||
_
|
||||
| |
|
||||
___| |_ __ _ _ ____ ____ _ _ __ ___
|
||||
/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __|
|
||||
\__ \ || (_) | | \ V V / (_) | | \__ \
|
||||
|___/\__\__/_|_| \_/\_/ \__/_|_| |___/
|
||||
__________________________________________`;
|
||||
SW5E.ASCII = `_______________________________
|
||||
______ ______ _____ _____
|
||||
| _ \\___ | _ \\ ___| ___|
|
||||
| | | ( _ ) | | | |___ \\| |__
|
||||
| | | / _ \\/\\ | | | \\ \\ __|
|
||||
| |/ / (_> < |/ //\\__/ / |___
|
||||
|___/ \\___/\\/___/ \\____/\\____/
|
||||
_______________________________`;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -43,15 +43,15 @@ SW5E.abilityAbbreviations = {
|
|||
* @type {Object}
|
||||
*/
|
||||
SW5E.alignments = {
|
||||
'll': "SW5E.AlignmentLL",
|
||||
'nl': "SW5E.AlignmentNL",
|
||||
'cl': "SW5E.AlignmentCL",
|
||||
'lb': "SW5E.AlignmentLB",
|
||||
'bn': "SW5E.AlignmentBN",
|
||||
'cb': "SW5E.AlignmentCB",
|
||||
'ld': "SW5E.AlignmentLD",
|
||||
'nd': "SW5E.AlignmentND",
|
||||
'cd': "SW5E.AlignmentCD"
|
||||
'lg': "SW5E.AlignmentLG",
|
||||
'ng': "SW5E.AlignmentNG",
|
||||
'cg': "SW5E.AlignmentCG",
|
||||
'ln': "SW5E.AlignmentLN",
|
||||
'tn': "SW5E.AlignmentTN",
|
||||
'cn': "SW5E.AlignmentCN",
|
||||
'le': "SW5E.AlignmentLE",
|
||||
'ne': "SW5E.AlignmentNE",
|
||||
'ce': "SW5E.AlignmentCE"
|
||||
};
|
||||
|
||||
|
||||
|
@ -61,35 +61,15 @@ SW5E.weaponProficiencies = {
|
|||
};
|
||||
|
||||
SW5E.toolProficiencies = {
|
||||
"armor": "SW5E.ToolArmormech",
|
||||
"arms": "SW5E.ToolArmstech",
|
||||
"arti": "SW5E.ToolArtificer",
|
||||
"art": "SW5E.ToolArtist",
|
||||
"astro": "SW5E.ToolAstrotech",
|
||||
"bio": "SW5E.ToolBiotech",
|
||||
"con": "SW5E.ToolConstructor",
|
||||
"cyb": "SW5E.ToolCybertech",
|
||||
"jew": "SW5E.ToolJeweler",
|
||||
"sur": "SW5E.ToolSurveyor",
|
||||
"syn": "SW5E.ToolSynthweaver",
|
||||
"tin": "SW5E.ToolTinker",
|
||||
"ant": "SW5E.ToolAntitoxkit",
|
||||
"arc": "SW5E.ToolArchaeologistKit",
|
||||
"aud": "SW5E.ToolAudiotechKit",
|
||||
"bioa": "SW5E.ToolBioanalysisKit",
|
||||
"brew": "SW5E.ToolBrewerKit",
|
||||
"chef": "SW5E.ToolChefKit",
|
||||
"demo": "SW5E.ToolDemolitionKit",
|
||||
"art": "SW5E.ToolArtisans",
|
||||
"disg": "SW5E.ToolDisguiseKit",
|
||||
"forg": "SW5E.ToolForgeryKit",
|
||||
"mech": "SW5E.ToolMechanicKit",
|
||||
"game": "SW5E.ToolGamingSet",
|
||||
"poi": "SW5E.ToolPoisonKit",
|
||||
"scav": "SW5E.ToolScavengingKit",
|
||||
"secur": "SW5E.ToolSecurityKit",
|
||||
"slic": "SW5E.ToolSlicerKit",
|
||||
"spice": "SW5E.ToolSpiceKit",
|
||||
"herb": "SW5E.ToolHerbalismKit",
|
||||
"music": "SW5E.ToolMusicalInstrument",
|
||||
"navg": "SW5E.ToolNavigators",
|
||||
"pois": "SW5E.ToolPoisonersKit",
|
||||
"thief": "SW5E.ToolThieves",
|
||||
"vehicle": "SW5E.ToolVehicle"
|
||||
};
|
||||
|
||||
|
@ -97,7 +77,7 @@ SW5E.toolProficiencies = {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* This Object defines the various lengths of time which can occur in SW5e
|
||||
* This Object defines the various lengths of time which can occur
|
||||
* @type {Object}
|
||||
*/
|
||||
SW5E.timePeriods = {
|
||||
|
@ -175,8 +155,8 @@ SW5E.tokenSizes = {
|
|||
SW5E.itemActionTypes = {
|
||||
"mwak": "SW5E.ActionMWAK",
|
||||
"rwak": "SW5E.ActionRWAK",
|
||||
"mpak": "SW5E.ActionMPAK",
|
||||
"rpak": "SW5E.ActionRPAK",
|
||||
"msak": "SW5E.ActionMSAK",
|
||||
"rsak": "SW5E.ActionRSAK",
|
||||
"save": "SW5E.ActionSave",
|
||||
"heal": "SW5E.ActionHeal",
|
||||
"abil": "SW5E.ActionAbil",
|
||||
|
@ -245,16 +225,14 @@ SW5E.armorProficiencies = {
|
|||
* @type {Object}
|
||||
*/
|
||||
SW5E.consumableTypes = {
|
||||
"adrenal": "SW5E.ConsumableAdrenal",
|
||||
"ammo": "SW5E.ConsumableAmmunition",
|
||||
"potion": "SW5E.ConsumablePotion",
|
||||
"poison": "SW5E.ConsumablePoison",
|
||||
"explosive": "SW5E.ConsumableExplosive",
|
||||
"food": "SW5E.ConsumableFood",
|
||||
"medpac": "SW5E.ConsumableMedpac",
|
||||
"technology": "SW5E.ConsumableTechnology",
|
||||
"ammunition": "SW5E.ConsumableAmmunition",
|
||||
"trinket": "SW5E.ConsumableTrinket",
|
||||
"force": "SW5E.ConsumableForce",
|
||||
"tech": "SW5E.ConsumableTech"
|
||||
"scroll": "SW5E.ConsumableScroll",
|
||||
"wand": "SW5E.ConsumableWand",
|
||||
"rod": "SW5E.ConsumableRod",
|
||||
"trinket": "SW5E.ConsumableTrinket"
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -264,8 +242,24 @@ SW5E.consumableTypes = {
|
|||
* @type {Object}
|
||||
*/
|
||||
SW5E.currencies = {
|
||||
"CR": "SW5E.CurrencyCR",
|
||||
};
|
||||
"pp": "SW5E.CurrencyPP",
|
||||
"gp": "SW5E.CurrencyGP",
|
||||
"ep": "SW5E.CurrencyEP",
|
||||
"sp": "SW5E.CurrencySP",
|
||||
"cp": "SW5E.CurrencyCP",
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Define the upwards-conversion rules for registered currency types
|
||||
* @type {{string, object}}
|
||||
*/
|
||||
SW5E.currencyConversion = {
|
||||
cp: {into: "sp", each: 10},
|
||||
sp: {into: "ep", each: 5 },
|
||||
ep: {into: "gp", each: 2 },
|
||||
gp: {into: "pp", each: 10}
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -273,69 +267,54 @@ SW5E.currencies = {
|
|||
// Damage Types
|
||||
SW5E.damageTypes = {
|
||||
"acid": "SW5E.DamageAcid",
|
||||
"bludgeoning": "SW5E.DamageBludgeoning",
|
||||
"cold": "SW5E.DamageCold",
|
||||
"energy": "SW5E.DamageEnergy",
|
||||
"fire": "SW5E.DamageFire",
|
||||
"force": "SW5E.DamageForce",
|
||||
"ion": "SW5E.DamageIon",
|
||||
"kinetic": "SW5E.DamageKinetic",
|
||||
"lightning": "SW5E.DamageLightning",
|
||||
"necrotic": "SW5E.DamageNecrotic",
|
||||
"piercing": "SW5E.DamagePiercing",
|
||||
"poison": "SW5E.DamagePoison",
|
||||
"psychic": "SW5E.DamagePsychic",
|
||||
"sonic": "SW5E.DamageSonic"
|
||||
"radiant": "SW5E.DamageRadiant",
|
||||
"slashing": "SW5E.DamageSlashing",
|
||||
"thunder": "SW5E.DamageThunder"
|
||||
};
|
||||
|
||||
// Damage Resistance Types
|
||||
SW5E.damageResistanceTypes = duplicate(SW5E.damageTypes);
|
||||
SW5E.damageResistanceTypes = mergeObject(duplicate(SW5E.damageTypes), {
|
||||
"physical": "SW5E.DamagePhysical"
|
||||
});
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// armor Types
|
||||
SW5E.armorPropertiesTypes = {
|
||||
"Absorptive": "SW5E.ArmorProperAbsorptive",
|
||||
"Agile": "SW5E.ArmorProperAgile",
|
||||
"Anchor": "SW5E.ArmorProperAnchor",
|
||||
"Avoidant": "SW5E.ArmorProperAvoidant",
|
||||
"Barbed": "SW5E.ArmorProperBarbed",
|
||||
"Bulky": "SW5E.ArmorProperBulky",
|
||||
"Charging": "SW5E.ArmorProperCharging",
|
||||
"Concealing": "SW5E.ArmorProperConcealing",
|
||||
"Cumbersome": "SW5E.ArmorProperCumbersome",
|
||||
"Gauntleted": "SW5E.ArmorProperGauntleted",
|
||||
"Imbalanced": "SW5E.ArmorProperImbalanced",
|
||||
"Impermeable": "SW5E.ArmorProperImpermeable",
|
||||
"Insulated": "SW5E.ArmorProperInsulated",
|
||||
"Interlocking": "SW5E.ArmorProperInterlocking",
|
||||
"Lambent": "SW5E.ArmorProperLambent",
|
||||
"Lightweight": "SW5E.ArmorProperLightweight",
|
||||
"Magnetic": "SW5E.ArmorProperMagnetic",
|
||||
"Obscured": "SW5E.ArmorProperObscured",
|
||||
"Obtrusive": "SW5E.ArmorProperObtrusive",
|
||||
"Powered": "SW5E.ArmorProperPowered",
|
||||
"Reactive": "SW5E.ArmorProperReactive",
|
||||
"Regulated": "SW5E.ArmorProperRegulated",
|
||||
"Reinforced": "SW5E.ArmorProperReinforced",
|
||||
"Responsive": "SW5E.ArmorProperResponsive",
|
||||
"Rigid": "SW5E.ArmorProperRigid",
|
||||
"Silent": "SW5E.ArmorProperSilent",
|
||||
"Spiked": "SW5E.ArmorProperSpiked",
|
||||
"Strength": "SW5E.ArmorProperStrength",
|
||||
"Steadfast": "SW5E.ArmorProperSteadfast",
|
||||
"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;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -353,14 +332,13 @@ SW5E.encumbrance = {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* This Object defines the types of single or area targets which can be applied in SW5e
|
||||
* This Object defines the types of single or area targets which can be applied
|
||||
* @type {Object}
|
||||
*/
|
||||
SW5E.targetTypes = {
|
||||
"none": "SW5E.None",
|
||||
"self": "SW5E.TargetSelf",
|
||||
"creature": "SW5E.TargetCreature",
|
||||
"droid": "SW5E.TargetDroid",
|
||||
"ally": "SW5E.TargetAlly",
|
||||
"enemy": "SW5E.TargetEnemy",
|
||||
"object": "SW5E.TargetObject",
|
||||
|
@ -372,8 +350,7 @@ SW5E.targetTypes = {
|
|||
"square": "SW5E.TargetSquare",
|
||||
"cube": "SW5E.TargetCube",
|
||||
"line": "SW5E.TargetLine",
|
||||
"wall": "SW5E.TargetWall",
|
||||
"weapon": "SW5E.TargetWeapon"
|
||||
"wall": "SW5E.TargetWall"
|
||||
};
|
||||
|
||||
|
||||
|
@ -410,10 +387,10 @@ SW5E.healingTypes = {
|
|||
|
||||
|
||||
/**
|
||||
* Enumerate the denominations of hit dice which can apply to classes in the SW5E system
|
||||
* Enumerate the denominations of hit dice which can apply to classes
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12"];
|
||||
SW5E.hitDieTypes = ["d6", "d8", "d10", "d12"];
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -433,46 +410,49 @@ SW5E.senses = {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The set of skill which can be trained in SW5e
|
||||
* The set of skill which can be trained
|
||||
* @type {Object}
|
||||
*/
|
||||
SW5E.skills = {
|
||||
"acr": "SW5E.SkillAcr",
|
||||
"ani": "SW5E.SkillAni",
|
||||
"arc": "SW5E.SkillArc",
|
||||
"ath": "SW5E.SkillAth",
|
||||
"dec": "SW5E.SkillDec",
|
||||
"his": "SW5E.SkillHis",
|
||||
"ins": "SW5E.SkillIns",
|
||||
"itm": "SW5E.SkillItm",
|
||||
"inv": "SW5E.SkillInv",
|
||||
"lor": "SW5E.SkillLor",
|
||||
"med": "SW5E.SkillMed",
|
||||
"nat": "SW5E.SkillNat",
|
||||
"prc": "SW5E.SkillPrc",
|
||||
"prf": "SW5E.SkillPrf",
|
||||
"per": "SW5E.SkillPer",
|
||||
"pil": "SW5E.SkillPil",
|
||||
"rel": "SW5E.SkillRel",
|
||||
"slt": "SW5E.SkillSlt",
|
||||
"ste": "SW5E.SkillSte",
|
||||
"sur": "SW5E.SkillSur",
|
||||
"tec": "SW5E.SkillTec"
|
||||
"sur": "SW5E.SkillSur"
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
SW5E.powerPreparationModes = {
|
||||
"prepared": "SW5E.PowerPrepPrepared",
|
||||
"pact": "SW5E.PactMagic",
|
||||
"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",
|
||||
"half": "SW5E.PowerProgHalf",
|
||||
"third": "SW5E.PowerProgThird",
|
||||
"pact": "SW5E.PowerProgPact",
|
||||
"artificer": "SW5E.PowerProgArt"
|
||||
};
|
||||
|
||||
|
@ -484,7 +464,7 @@ SW5E.powerProgression = {
|
|||
*/
|
||||
SW5E.powerScalingModes = {
|
||||
"none": "SW5E.PowerNone",
|
||||
"atwill": "SW5E.PowerAtWill",
|
||||
"cantrip": "SW5E.PowerCantrip",
|
||||
"level": "SW5E.PowerLevel"
|
||||
};
|
||||
|
||||
|
@ -496,15 +476,12 @@ SW5E.powerScalingModes = {
|
|||
* @type {Object}
|
||||
*/
|
||||
SW5E.weaponTypes = {
|
||||
"simpleVW": "SW5E.WeaponSimpleVW",
|
||||
"simpleB": "SW5E.WeaponSimpleB",
|
||||
"simpleLW": "SW5E.WeaponSimpleLW",
|
||||
"martialVW": "SW5E.WeaponMartialVW",
|
||||
"martialB": "SW5E.WeaponMartialB",
|
||||
"martialLW": "SW5E.WeaponMartialLW",
|
||||
"simpleM": "SW5E.WeaponSimpleM",
|
||||
"simpleR": "SW5E.WeaponSimpleR",
|
||||
"martialM": "SW5E.WeaponMartialM",
|
||||
"martialR": "SW5E.WeaponMartialR",
|
||||
"natural": "SW5E.WeaponNatural",
|
||||
"improv": "SW5E.WeaponImprov",
|
||||
"ammo": "SW5E.WeaponAmmo",
|
||||
"siege": "SW5E.WeaponSiege"
|
||||
};
|
||||
|
||||
|
@ -517,38 +494,19 @@ SW5E.weaponTypes = {
|
|||
*/
|
||||
SW5E.weaponProperties = {
|
||||
"amm": "SW5E.WeaponPropertiesAmm",
|
||||
"aut": "SW5E.WeaponPropertiesAut",
|
||||
"bur": "SW5E.WeaponPropertiesBur",
|
||||
"def": "SW5E.WeaponPropertiesDef",
|
||||
"dex": "SW5E.WeaponPropertiesDex",
|
||||
"dir": "SW5E.WeaponPropertiesDir",
|
||||
"drm": "SW5E.WeaponPropertiesDrm",
|
||||
"dgd": "SW5E.WeaponPropertiesDgd",
|
||||
"dis": "SW5E.WeaponPropertiesDis",
|
||||
"dpt": "SW5E.WeaponPropertiesDpt",
|
||||
"dou": "SW5E.WeaponPropertiesDou",
|
||||
"fin": "SW5E.WeaponPropertiesFin",
|
||||
"fix": "SW5E.WeaponPropertiesFix",
|
||||
"foc": "SW5E.WeaponPropertiesFoc",
|
||||
"hvy": "SW5E.WeaponPropertiesHvy",
|
||||
"hid": "SW5E.WeaponPropertiesHid",
|
||||
"ken": "SW5E.WeaponPropertiesKen",
|
||||
"fin": "SW5E.WeaponPropertiesFin",
|
||||
"fir": "SW5E.WeaponPropertiesFir",
|
||||
"foc": "SW5E.WeaponPropertiesFoc",
|
||||
"lgt": "SW5E.WeaponPropertiesLgt",
|
||||
"lum": "SW5E.WeaponPropertiesLum",
|
||||
"mig": "SW5E.WeaponPropertiesMig",
|
||||
"pic": "SW5E.WeaponPropertiesPic",
|
||||
"rap": "SW5E.WeaponPropertiesRap",
|
||||
"lod": "SW5E.WeaponPropertiesLod",
|
||||
"rch": "SW5E.WeaponPropertiesRch",
|
||||
"rel": "SW5E.WeaponPropertiesRel",
|
||||
"ret": "SW5E.WeaponPropertiesRet",
|
||||
"shk": "SW5E.WeaponPropertiesShk",
|
||||
"sil": "SW5E.WeaponPropertiesSil",
|
||||
"spc": "SW5E.WeaponPropertiesSpc",
|
||||
"str": "SW5E.WeaponPropertiesStr",
|
||||
"thr": "SW5E.WeaponPropertiesThr",
|
||||
"two": "SW5E.WeaponPropertiesTwo",
|
||||
"ver": "SW5E.WeaponPropertiesVer",
|
||||
"vic": "SW5E.WeaponPropertiesVic"
|
||||
"ver": "SW5E.WeaponPropertiesVer"
|
||||
};
|
||||
|
||||
|
||||
|
@ -561,11 +519,14 @@ SW5E.powerComponents = {
|
|||
|
||||
// Power Schools
|
||||
SW5E.powerSchools = {
|
||||
"lgt": "SW5E.SchoolLgt",
|
||||
"uni": "SW5E.SchoolUni",
|
||||
"drk": "SW5E.SchoolDrk",
|
||||
"tec": "SW5E.SchoolTec",
|
||||
"enh": "SW5E.SchoolEnh"
|
||||
"abj": "SW5E.SchoolAbj",
|
||||
"con": "SW5E.SchoolCon",
|
||||
"div": "SW5E.SchoolDiv",
|
||||
"enc": "SW5E.SchoolEnc",
|
||||
"evo": "SW5E.SchoolEvo",
|
||||
"ill": "SW5E.SchoolIll",
|
||||
"nec": "SW5E.SchoolNec",
|
||||
"trs": "SW5E.SchoolTrs"
|
||||
};
|
||||
|
||||
// Power Levels
|
||||
|
@ -582,6 +543,20 @@ SW5E.powerLevels = {
|
|||
9: "SW5E.PowerLevel9"
|
||||
};
|
||||
|
||||
// Power Scroll Compendium UUIDs
|
||||
SW5E.powerScrollIds = {
|
||||
0: 'Compendium.sw5e.items.rQ6sO7HDWzqMhSI3',
|
||||
1: 'Compendium.sw5e.items.9GSfMg0VOA2b4uFN',
|
||||
2: 'Compendium.sw5e.items.XdDp6CKh9qEvPTuS',
|
||||
3: 'Compendium.sw5e.items.hqVKZie7x9w3Kqds',
|
||||
4: 'Compendium.sw5e.items.DM7hzgL836ZyUFB1',
|
||||
5: 'Compendium.sw5e.items.wa1VF8TXHmkrrR35',
|
||||
6: 'Compendium.sw5e.items.tI3rWx4bxefNCexS',
|
||||
7: 'Compendium.sw5e.items.mtyw4NS1s7j2EJaD',
|
||||
8: 'Compendium.sw5e.items.aOrinPg7yuDZEuWr',
|
||||
9: 'Compendium.sw5e.items.O4YbkJkLlnsgUszZ'
|
||||
};
|
||||
|
||||
/**
|
||||
* Define the standard slot progression by character level.
|
||||
* The entries of this array represent the power slot progression for a full power-caster.
|
||||
|
@ -675,114 +650,37 @@ SW5E.conditionTypes = {
|
|||
"poisoned": "SW5E.ConPoisoned",
|
||||
"prone": "SW5E.ConProne",
|
||||
"restrained": "SW5E.ConRestrained",
|
||||
"shocked": "SW5E.ConShocked",
|
||||
"slowed": "SW5E.ConSlowed",
|
||||
"stunned": "SW5E.ConStunned",
|
||||
"unconscious": "SW5E.ConUnconscious"
|
||||
};
|
||||
|
||||
// Languages
|
||||
SW5E.languages = {
|
||||
"aleena": "SW5E.LanguagesAleena",
|
||||
"antarian": "SW5E.LanguagesAntarian",
|
||||
"anzellan": "SW5E.LanguagesAnzellan",
|
||||
"aqualish": "SW5E.LanguagesAqualish",
|
||||
"ardennian": "SW5E.LanguagesArdennian",
|
||||
"balosur": "SW5E.LanguagesBalosur",
|
||||
"barabel": "SW5E.LanguagesBarabel",
|
||||
"basic": "SW5E.LanguagesBasic",
|
||||
"besalisk": "SW5E.LanguagesBesalisk",
|
||||
"binary": "SW5E.LanguagesBinary",
|
||||
"bith": "SW5E.LanguagesBith",
|
||||
"bocce": "SW5E.LanguagesBocce",
|
||||
"bothese": "SW5E.LanguagesBothese",
|
||||
"catharese": "SW5E.LanguagesCatharese",
|
||||
"cerean": "SW5E.LanguagesCerean",
|
||||
"chadra-fan": "SW5E.LanguagesChadra-Fan",
|
||||
"chagri": "SW5E.LanguagesChagri",
|
||||
"cheunh": "SW5E.LanguagesCheunh",
|
||||
"chevin": "SW5E.LanguagesChevin",
|
||||
"chironan": "SW5E.LanguagesChironan",
|
||||
"clawdite": "SW5E.LanguagesClawdite",
|
||||
"codruese": "SW5E.LanguagesCodruese",
|
||||
"colicoid": "SW5E.LanguagesColicoid",
|
||||
"dashadi": "SW5E.LanguagesDashadi",
|
||||
"defel": "SW5E.LanguagesDefel",
|
||||
"devaronese": "SW5E.LanguagesDevaronese",
|
||||
"dosh": "SW5E.LanguagesDosh",
|
||||
"draethos": "SW5E.LanguagesDraethos",
|
||||
"durese": "SW5E.LanguagesDurese",
|
||||
"dug": "SW5E.LanguagesDug",
|
||||
"ewokese": "SW5E.LanguagesEwokese",
|
||||
"falleen": "SW5E.LanguagesFalleen",
|
||||
"felucianese": "SW5E.LanguagesFelucianese",
|
||||
"gamorrese": "SW5E.LanguagesGamorrese",
|
||||
"gand": "SW5E.LanguagesGand",
|
||||
"geonosian": "SW5E.LanguagesGeonosian",
|
||||
"givin": "SW5E.LanguagesGivin",
|
||||
"gran": "SW5E.LanguagesGran",
|
||||
"gungan": "SW5E.LanguagesGungan",
|
||||
"hapan": "SW5E.LanguagesHapan",
|
||||
"harchese": "SW5E.LanguagesHarchese",
|
||||
"herglese": "SW5E.LanguagesHerglese",
|
||||
"honoghran": "SW5E.LanguagesHonoghran",
|
||||
"huttese": "SW5E.LanguagesHuttese",
|
||||
"iktotchese": "SW5E.LanguagesIktotchese",
|
||||
"ithorese": "SW5E.LanguagesIthorese",
|
||||
"jawaese": "SW5E.LanguagesJawaese",
|
||||
"kaleesh": "SW5E.LanguagesKaleesh",
|
||||
"kaminoan": "SW5E.LanguagesKaminoan",
|
||||
"karkaran": "SW5E.LanguagesKarkaran",
|
||||
"keldor": "SW5E.LanguagesKelDor",
|
||||
"kharan": "SW5E.LanguagesKharan",
|
||||
"killik": "SW5E.LanguagesKillik",
|
||||
"klatooinian": "SW5E.LanguagesKlatooinian",
|
||||
"kubazian": "SW5E.LanguagesKubazian",
|
||||
"kushiban": "SW5E.LanguagesKushiban",
|
||||
"kyuzo": "SW5E.LanguagesKyuzo",
|
||||
"lannik": "SW5E.LanguagesLannik",
|
||||
"lasat": "SW5E.LanguagesLasat",
|
||||
"lowickese": "SW5E.LanguagesLowickese",
|
||||
"mandoa": "SW5E.LanguagesMandoa",
|
||||
"miralukese": "SW5E.LanguagesMiralukese",
|
||||
"mirialan": "SW5E.LanguagesMirialan",
|
||||
"moncal": "SW5E.LanguagesMonCal",
|
||||
"mustafarian": "SW5E.LanguagesMustafarian",
|
||||
"muun": "SW5E.LanguagesMuun",
|
||||
"nautila": "SW5E.LanguagesNautila",
|
||||
"ortolan": "SW5E.LanguagesOrtolan",
|
||||
"pakpak": "SW5E.LanguagesPakPak",
|
||||
"pyke": "SW5E.LanguagesPyke",
|
||||
"quarrenese": "SW5E.LanguagesQuarrenese",
|
||||
"rakata": "SW5E.LanguagesRakata",
|
||||
"rattataki": "SW5E.LanguagesRattataki",
|
||||
"rishii": "SW5E.LanguagesRishii",
|
||||
"rodese": "SW5E.LanguagesRodese",
|
||||
"selkatha": "SW5E.LanguagesSelkatha",
|
||||
"semblan": "SW5E.LanguagesSemblan",
|
||||
"shistavanen": "SW5E.LanguagesShistavanen",
|
||||
"shyriiwook": "SW5E.LanguagesShyriiwook",
|
||||
"sith": "SW5E.LanguagesSith",
|
||||
"squibbian": "SW5E.LanguagesSquibbian",
|
||||
"sriluurian": "SW5E.LanguagesSriluurian",
|
||||
"ssi-ruuvi": "SW5E.LanguagesSsi-ruuvi",
|
||||
"sullustese": "SW5E.LanguagesSullustese",
|
||||
"talzzi": "SW5E.LanguagesTalzzi",
|
||||
"thisspiasian": "SW5E.LanguagesThisspiasian",
|
||||
"togorese": "SW5E.LanguagesTogorese",
|
||||
"togruti": "SW5E.LanguagesTogruti",
|
||||
"toydarian": "SW5E.LanguagesToydarian",
|
||||
"tusken": "SW5E.LanguagesTusken",
|
||||
"twi'leki": "SW5E.LanguagesTwileki",
|
||||
"ugnaught": "SW5E.LanguagesUgnaught",
|
||||
"umbaran": "SW5E.LanguagesUmbaran",
|
||||
"utapese": "SW5E.LanguagesUtapese",
|
||||
"verpine": "SW5E.LanguagesVerpine",
|
||||
"vong": "SW5E.LanguagesVong",
|
||||
"voss": "SW5E.LanguagesVoss",
|
||||
"yevethan": "SW5E.LanguagesYevethan",
|
||||
"zabraki": "SW5E.LanguagesZabraki",
|
||||
"zygerrian": "SW5E.LanguagesZygerrian"
|
||||
"common": "SW5E.LanguagesCommon",
|
||||
"aarakocra": "SW5E.LanguagesAarakocra",
|
||||
"abyssal": "SW5E.LanguagesAbyssal",
|
||||
"aquan": "SW5E.LanguagesAquan",
|
||||
"auran": "SW5E.LanguagesAuran",
|
||||
"celestial": "SW5E.LanguagesCelestial",
|
||||
"deep": "SW5E.LanguagesDeepSpeech",
|
||||
"draconic": "SW5E.LanguagesDraconic",
|
||||
"druidic": "SW5E.LanguagesDruidic",
|
||||
"dwarvish": "SW5E.LanguagesDwarvish",
|
||||
"elvish": "SW5E.LanguagesElvish",
|
||||
"giant": "SW5E.LanguagesGiant",
|
||||
"gith": "SW5E.LanguagesGith",
|
||||
"gnomish": "SW5E.LanguagesGnomish",
|
||||
"goblin": "SW5E.LanguagesGoblin",
|
||||
"gnoll": "SW5E.LanguagesGnoll",
|
||||
"halfling": "SW5E.LanguagesHalfling",
|
||||
"ignan": "SW5E.LanguagesIgnan",
|
||||
"infernal": "SW5E.LanguagesInfernal",
|
||||
"orc": "SW5E.LanguagesOrc",
|
||||
"primordial": "SW5E.LanguagesPrimordial",
|
||||
"sylvan": "SW5E.LanguagesSylvan",
|
||||
"terran": "SW5E.LanguagesTerran",
|
||||
"cant": "SW5E.LanguagesThievesCant",
|
||||
"undercommon": "SW5E.LanguagesUndercommon"
|
||||
};
|
||||
|
||||
// Character Level XP Requirements
|
||||
|
@ -802,57 +700,21 @@ SW5E.classFeatures = ClassFeatures;
|
|||
|
||||
// Configure Optional Character Flags
|
||||
SW5E.characterFlags = {
|
||||
"detailOriented": {
|
||||
name: "Detail Oriented",
|
||||
hint: "You have advantage on Intelligence (Investigation) checks within 5 feet.",
|
||||
"diamondSoul": {
|
||||
name: "SW5E.FlagsDiamondSoul",
|
||||
hint: "SW5E.FlagsDiamondSoulHint",
|
||||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"elvenAccuracy": {
|
||||
name: "SW5E.FlagsElvenAccuracy",
|
||||
hint: "SW5E.FlagsElvenAccuracyHint",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"keenSenses": {
|
||||
name: "Keen Hearing and Smell",
|
||||
hint: "You have advantage on Wisdom (Perception) checks that involve hearing or smell.",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"naturallyStealthy": {
|
||||
name: "Naturally Stealthy",
|
||||
hint: "You can attempt to hide even when you are obscured only by a creature that is your size or larger than you.",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"nimbleEscape": {
|
||||
name: "Nimble Escape",
|
||||
hint: "You can take the Disengage or Hide action as a bonus action.",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"powerfulBuild": {
|
||||
name: "SW5E.FlagsPowerfulBuild",
|
||||
hint: "SW5E.FlagsPowerfulBuildHint",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"programmer": {
|
||||
name: "Programmer",
|
||||
hint: "Whenever you make an Intelligence (Technology) check related to computers, you are considered to have expertise in the Technology skill.",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"techResistance": {
|
||||
name: "Tech Resistance",
|
||||
hint: "You have advantage on Dexterity and Intelligence saving throws against tech powers.",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"unarmedCombatant": {
|
||||
name: "Unarmed Combatant",
|
||||
hint: "Your unarmed strikes deal 1d4 kinetic damage. You can use your choice of your Strength or Dexterity modifier for the attack and damage rolls. You must use the same modifier for both rolls.",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"undersized": {
|
||||
name: "Undersized",
|
||||
hint: "You can’t use heavy shields, martial weapons with the two-handed property unless it also has the light property, and if a martial weapon has the versatile property, you can only wield it in two hands.",
|
||||
"halflingLucky": {
|
||||
name: "SW5E.FlagsHalflingLucky",
|
||||
hint: "SW5E.FlagsHalflingLuckyHint",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
|
@ -881,13 +743,19 @@ SW5E.characterFlags = {
|
|||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"powerfulBuild": {
|
||||
name: "SW5E.FlagsPowerfulBuild",
|
||||
hint: "SW5E.FlagsPowerfulBuildHint",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"reliableTalent": {
|
||||
name: "SW5E.FlagsReliableTalent",
|
||||
hint: "SW5E.FlagsReliableTalentHint",
|
||||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"remarkableAthlete": {
|
||||
"remarkableAthlete": {
|
||||
name: "SW5E.FlagsRemarkableAthlete",
|
||||
hint: "SW5E.FlagsRemarkableAthleteHint",
|
||||
abilities: ['str','dex','con'],
|
||||
|
@ -895,15 +763,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;
|
||||
}
|
|
@ -37,7 +37,7 @@ export default class Item5e extends Item {
|
|||
const wt = itemData.weaponType;
|
||||
|
||||
// Melee weapons - Str or Dex if Finesse (PHB pg. 147)
|
||||
if ( ["simpleVW", "martialVW", "simpleLW", "martialLW"].includes(wt) ) {
|
||||
if ( ["simpleM", "martialM"].includes(wt) ) {
|
||||
if (itemData.properties.fin === true) { // Finesse weapons
|
||||
return (actorData.abilities["dex"].mod >= actorData.abilities["str"].mod) ? "dex" : "str";
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export default class Item5e extends Item {
|
|||
}
|
||||
|
||||
// Ranged weapons - Dex (PH p.194)
|
||||
else if ( ["simpleB", "martialB"].includes(wt) ) return "dex";
|
||||
else if ( ["simpleR", "martialR"].includes(wt) ) return "dex";
|
||||
}
|
||||
return "str";
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ export default class Item5e extends Item {
|
|||
* @type {boolean}
|
||||
*/
|
||||
get hasAttack() {
|
||||
return ["mwak", "rwak", "mpak", "rpak"].includes(this.data.data.actionType);
|
||||
return ["mwak", "rwak", "msak", "rsak"].includes(this.data.data.actionType);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -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,19 +181,6 @@ 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" ) {
|
||||
// labels.archetype = C.archetype[data.archetype];
|
||||
}
|
||||
// Class Feature Items
|
||||
else if ( itemData.type === "classfeature" ) {
|
||||
|
||||
}
|
||||
|
||||
// Equipment Items
|
||||
else if ( itemData.type === "equipment" ) {
|
||||
labels.armor = data.armor.value ? `${data.armor.value} ${game.i18n.localize("SW5E.AC")}` : "";
|
||||
|
@ -306,7 +294,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,
|
||||
|
@ -332,7 +320,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)
|
||||
|
@ -351,7 +339,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;
|
||||
|
@ -364,6 +351,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":
|
||||
|
@ -377,7 +365,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;
|
||||
}
|
||||
|
||||
|
@ -402,7 +396,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;
|
||||
}
|
||||
|
@ -420,7 +418,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;
|
||||
|
||||
|
@ -621,40 +619,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,
|
||||
|
@ -665,9 +660,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
|
||||
|
@ -694,28 +691,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 ) {
|
||||
|
@ -725,9 +735,9 @@ export default class Item5e extends Item {
|
|||
|
||||
// Scale damage from up-casting powers
|
||||
if ( (this.data.type === "power") ) {
|
||||
if ( (itemData.scaling.mode === "atwill") ) {
|
||||
if ( (itemData.scaling.mode === "cantrip") ) {
|
||||
const level = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
|
||||
this._scaleAtWillDamage(parts, itemData.scaling.formula, level, rollData);
|
||||
this._scaleCantripDamage(parts, itemData.scaling.formula, level, rollData);
|
||||
}
|
||||
else if ( powerLevel && (itemData.scaling.mode === "level") && itemData.scaling.formula ) {
|
||||
const scaling = itemData.scaling.formula;
|
||||
|
@ -735,64 +745,39 @@ 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));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Adjust an at-will damage formula to scale it for higher level characters and monsters
|
||||
* Adjust a cantrip damage formula to scale it for higher level characters and monsters
|
||||
* @private
|
||||
*/
|
||||
_scaleAtWillDamage(parts, scale, level, rollData) {
|
||||
_scaleCantripDamage(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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -810,20 +795,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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -866,7 +838,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={}) {
|
||||
|
@ -883,7 +855,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 }}
|
||||
});
|
||||
|
@ -894,7 +866,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
|
||||
*/
|
||||
|
@ -956,7 +928,7 @@ export default class Item5e extends Item {
|
|||
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -1005,7 +977,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,
|
||||
|
@ -1039,8 +1011,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;
|
||||
}
|
||||
|
||||
|
@ -1173,7 +1145,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,15 +41,13 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
async getData(options) {
|
||||
const data = super.getData(options);
|
||||
data.labels = this.item.labels;
|
||||
|
||||
// Include CONFIG values
|
||||
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");
|
||||
|
@ -57,16 +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);
|
||||
|
||||
// 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
|
||||
|
@ -110,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" ?
|
||||
|
@ -117,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;
|
||||
}, {})
|
||||
}
|
||||
|
@ -176,18 +188,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
else if ( item.type === "feat" ) {
|
||||
props.push(labels.featType);
|
||||
}
|
||||
|
||||
else if ( item.type === "species" ) {
|
||||
|
||||
}
|
||||
else if ( item.type === "archetype" ) {
|
||||
|
||||
}
|
||||
|
||||
else if ( item.type === "classfeature" ) {
|
||||
|
||||
}
|
||||
|
||||
// Action type
|
||||
if ( item.data.actionType ) {
|
||||
props.push(CONFIG.SW5E.itemActionTypes[item.data.actionType]);
|
||||
|
@ -225,8 +226,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);
|
||||
}
|
||||
|
@ -236,17 +237,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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -254,8 +258,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)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -313,4 +323,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* @return {Promise} A Promise which resolves once the migration is completed
|
||||
*/
|
||||
export const migrateWorld = async function() {
|
||||
ui.notifications.info(`Applying SW5E System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
|
||||
ui.notifications.info(`Applying SW5e System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
|
||||
|
||||
// Migrate World Actors
|
||||
for ( let a of game.actors.entities ) {
|
||||
|
@ -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,21 +42,21 @@ 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);
|
||||
}
|
||||
|
||||
// Set the migration as complete
|
||||
game.settings.set("sw5e", "systemMigrationVersion", game.system.data.version);
|
||||
ui.notifications.info(`SW5E System Migration to version ${game.system.data.version} completed!`, {permanent: true});
|
||||
ui.notifications.info(`SW5e System Migration to version ${game.system.data.version} completed!`, {permanent: true});
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -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,8 +37,8 @@ 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
|
||||
templateData.width = canvas.dimensions.distance;
|
||||
case "ray": // 5e rays are most commonly 1 square (5 ft) in width
|
||||
templateData.width = target.width ?? canvas.dimensions.distance;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -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,24 +4,21 @@
|
|||
* @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/parts/actor-traits.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-inventory.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-features.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-powerbook.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-effects.html",
|
||||
|
||||
// Item Sheet Partials
|
||||
"systems/sw5e/templates/items/parts/item-action.html",
|
||||
"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);
|
||||
]);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue