Merge branch 'Develop' into professorbunbury-sw5e

This commit is contained in:
CK 2021-03-05 11:10:12 -05:00 committed by GitHub
commit 35460dd17a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
283 changed files with 3645 additions and 2540 deletions

2
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,2 @@
{
}

View file

@ -31,6 +31,6 @@ Please reach out on the SW5E Foundry Dev Discord with any questions.
## Compatible Modules and Optimum Settings
- DAE (Dynamic Active Effects) is needed for many automatic features.
-**Please enable: "Include active effects in special traits display" in "Configure Game Settings> Module Settings> Dynamic Active Effects".**
- **Please enable: "Include active effects in special traits display" in "Configure Game Settings> Module Settings> Dynamic Active Effects".**
- Midi QoL is compatible with great features
- Token Action Hud has compatibility

View file

@ -144,6 +144,10 @@
"SW5E.BonusRWDamage": "Ranged Weapon Damage Bonus",
"SW5E.BonusSaveForm": "Update Bonuses",
"SW5E.BonusPowerDC": "Global Power DC Bonus",
"SW5E.BonusForceLightPowerDC": "Global Force Light Power DC Bonus",
"SW5E.BonusForceDarkPowerDC": "Global Force Dark Power DC Bonus",
"SW5E.BonusForceUnivPowerDC": "Global Force Universal Power DC Bonus",
"SW5E.BonusTechPowerDC": "Global Tech Power DC Bonus",
"SW5E.BonusTitle": "Configure Actor Bonuses",
"SW5E.Bonuses": "Global Bonuses",
"SW5E.BonusesHint": "Define global bonuses as formulas which are added to certain rolls. For example: 1d4 + 2",
@ -588,10 +592,23 @@
"SW5E.LongRestGritty": "Long Rest (7 days)",
"SW5E.LongRestEpic": "Long Rest (1 hour)",
"SW5E.LongRestOvernight": "Long Rest (New Day)",
"SW5E.LongRestResult": "{name} takes a long rest and recovers {health} Hit Points and {dice} Hit Dice.",
"SW5E.LongRestResultHitDice": "{name} takes a long rest and recovers {dice} Hit Dice.",
"SW5E.LongRestResultHitPoints": "{name} takes a long rest and recovers {health} Hit Points.",
"SW5E.LongRestResultShort": "{name} takes a long rest.",
"SW5E.LongRestResult": "{name} takes a long rest.",
"SW5E.LongRestResultFP": "{name} takes a long rest and recovers {force} Force Points.",
"SW5E.LongRestResultFPHD": "{name} takes a long rest and recovers {force} Force Points and {dice} Hit Dice.",
"SW5E.LongRestResultFPTP": "{name} takes a long rest and recovers {force} Force Points and {tech} Tech Points.",
"SW5E.LongRestResultFPTPHD": "{name} takes a long rest and recovers {force} Force Points, {tech} Tech Points and {dice} Hit Dice.",
"SW5E.LongRestResultHD": "{name} takes a long rest and recovers {dice} Hit Dice.",
"SW5E.LongRestResultHP": "{name} takes a long rest and recovers {health} Hit Points.",
"SW5E.LongRestResultHPFP": "{name} takes a long rest and recovers {health} Hit Points and {force} Force Points.",
"SW5E.LongRestResultHPHD": "{name} takes a long rest and recovers {health} Hit Points and {dice} Hit Dice.",
"SW5E.LongRestResultHPTP": "{name} takes a long rest and recovers {health} Hit Points and {tech} Tech Points.",
"SW5E.LongRestResultHPFPHD": "{name} takes a long rest and recovers {health} Hit Points, {force} Force Points, and {dice} Hit Dice.",
"SW5E.LongRestResultHPFPTP": "{name} takes a long rest and recovers {health} Hit Points, {force} Force Points and {tech} Tech Points.",
"SW5E.LongRestResultHPTPHD": "{name} takes a long rest and recovers {health} Hit Points, {tech} Tech Points, and {dice} Hit Dice.",
"SW5E.LongRestResultHPFPTPHD": "{name} takes a long rest and recovers {health} Hit Points, {force} Force Points, {tech} Tech Points and {dice} Hit Dice.",
"SW5E.LongRestResultTP": "{name} takes a long rest and recovers {tech} Tech Points.",
"SW5E.LongRestResultTPHD": "{name} takes a long rest and recovers {tech} Tech Points and {dice} Hit Dice.",
"SW5E.Max": "Max",
"SW5E.Modifier": "Modifier",
"SW5E.Name": "Character Name",
@ -662,7 +679,6 @@
"SW5E.MovementFly": "Fly",
"SW5E.MovementSwim": "Swim",
"SW5E.MovementUnits": "Units",
"SW5E.Senses": "Senses",
"SW5E.SensesConfig": "Configure Senses",
"SW5E.SensesConfigHint": "Configure any special sensory perception abilities that this actor possesses.",
"SW5E.SenseDarkvision": "Darkvision",
@ -699,6 +715,8 @@
"SW5E.ShortRestHint": "Take a short rest? On a short rest you may spend remaining Hit Dice and recover primary or secondary resources.",
"SW5E.ShortRestNoHD": "No Hit Dice remaining",
"SW5E.ShortRestResult": "{name} takes a short rest spending {dice} Hit Dice to recover {health} Hit Points.",
"SW5E.ShortRestResultWithTech": "{name} takes a short rest spending {dice} Hit Dice to recover {health} Hit Points and {tech} Tech Points.",
"SW5E.ShortRestResultOnlyTech": "{name} takes a short rest to recover {tech} Tech Points.",
"SW5E.ShortRestResultShort": "{name} takes a short rest.",
"SW5E.ShortRestSelect": "Select Dice to Roll",
"SW5E.Size": "Size",
@ -746,8 +764,13 @@
"SW5E.PowerComponents": "Power Components",
"SW5E.PowerCreate": "Create Power",
"SW5E.PowerDC": "Power DC",
"SW5E.UniversalPowerDC": "Universal Power DC",
"SW5E.LightPowerDC": "Light Power DC",
"SW5E.DarkPowerDC": "Dark Power DC",
"SW5E.TechPowerDC": "Tech Power DC",
"SW5E.PowerDetails": "Power Details",
"SW5E.PowerEffects": "Power Effects",
"SW5E.PowersKnown": "Powers Known",
"SW5E.PowerLevel": "Power Level",
"SW5E.PowerLevel0": "At-Will",
"SW5E.PowerLevel1": "1st Level",
@ -772,8 +795,11 @@
"SW5E.PowerPrepared": "Prepared",
"SW5E.PowerConcentrationMode": "Power Concentration Mode",
"SW5E.PowerConcentrating": "Concentrating",
"SW5E.PowerProgArt": "Artificer",
"SW5E.PowerProgFull": "Full Caster",
"SW5E.PowerProgCns": "Consular",
"SW5E.PowerProgEng": "Engineer",
"SW5E.PowerProgGrd": "Guardian",
"SW5E.PowerProgSct": "Scout",
"SW5E.PowerProgSnt": "Sentinel",
"SW5E.PowerProgOverride": "Override slots",
"SW5E.PowerProgression": "Power Progression",
"SW5E.PowerSchool": "Power School",
@ -781,6 +807,8 @@
"SW5E.PowerUnprepared": "Unprepared",
"SW5E.PowerUsage": "Power Usage",
"SW5E.Powerbook": "Powerbook",
"SW5E.ForcePowerbook": "Force Powers",
"SW5E.TechPowerbook": "Tech Powers",
"SW5E.SpeciesDescription": "Description",
"SW5E.SpeciesTraits": "Species Traits",
"SW5E.StealthDisadvantage": "Stealth Disadvantage",

View file

@ -400,7 +400,8 @@
.tab.features,
.tab.inventory,
.tab.powerbook {
.tab.force-powerbook,
.tab.tech-powerbook {
overflow-y: hidden;
}

View file

@ -4,7 +4,7 @@
/* Basic Structure */
/* ----------------------------------------- */
.sw5e.sheet.actor.npc {
min-width: 600px;
min-width: 872px;
min-height: 680px;
.header-exp {

View file

@ -13,7 +13,7 @@
@blockquoteShadow: 0 0 20px rgba(@colorBlue, 0.8);
//forms
@inputBackgroundColor: @colorGray;
@inputBackgroundColor: white;
@inputBorderNormal: @colorLightGray;
@inputBorderHover: @colorGray;
@inputBorderFocus: @colorRed;

View file

@ -41,7 +41,7 @@
//SW5e Colors
@colorBlack: #1C1C1C;
@colorDarkGray: #363636;
@colorGray: #a9a9a9;
@colorGray: #4f4f4f;
@colorLightGray: #828282;
@colorPaleGray: #D6D6D6;
@colorRed: #c40f0f;

File diff suppressed because it is too large Load diff

View file

@ -382,7 +382,8 @@
}
.tab.powerbook {
.tab.force-powerbook,
.tab.tech-powerbook {
.powercasting-ability {
label,
h3 {

View file

@ -1,6 +1,5 @@
input[type="text"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], select, textarea {
border: 1px solid @inputBorderNormal;
color: @inputTextColor;
border: 1px solid @inputBorderNormal;
&:hover {
border-color: @inputBorderHover;
}
@ -50,4 +49,4 @@ form {
.notes, .hint {
color: rgba(@bodyFontColor, 0.8);
}
}
}

View file

@ -39,9 +39,19 @@ body.dark-theme {
border-width: 0 0 1px 0;
border-bottom: 1px solid @hrColor;
}
select {
color: white;
background-color: rgba(0, 0, 0, 0.5);
}
input[type="text"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], select, textarea {
color: @inputTextColor;
}
@import "components/forms-themes.less";
@import "components/sidebar-themes.less";
@import "components/foundry-nav-themes.less";
@import "components/foundry-app-window-themes.less";
@import "components/actor-themes.less";
}
}

View file

@ -92,7 +92,10 @@ export default class Actor5e extends Actor {
init.total = init.mod + init.prof + init.bonus;
// Prepare power-casting data
data.attributes.powerdc = data.attributes.powercasting ? data.abilities[data.attributes.powercasting].dc : 10;
data.attributes.powerForceLightDC = 8 + data.abilities.wis.mod + data.attributes.prof ?? 10;
data.attributes.powerForceDarkDC = 8 + data.abilities.cha.mod + data.attributes.prof ?? 10;
data.attributes.powerForceUnivDC = Math.max(data.attributes.powerForceLightDC,data.attributes.powerForceDarkDC) ?? 10;
data.attributes.powerTechDC = 8 + data.abilities.int.mod + data.attributes.prof ?? 10;
this._computePowercastingProgression(this.data);
// Compute owned item attributes which depend on prepared Actor data
@ -364,75 +367,213 @@ export default class Actor5e extends Actor {
const powers = actorData.data.powers;
const isNPC = actorData.type === 'npc';
// Translate the list of classes into power-casting progression
const progression = {
total: 0,
slot: 0,
pact: 0
// Translate the list of classes into force and tech power-casting progression
const forceProgression = {
classes: 0,
levels: 0,
multi: 0,
maxClass: "none",
maxClassPriority: 0,
maxClassLevels: 0,
maxClassPowerLevel: 0,
powersKnown: 0,
points: 0
};
const techProgression = {
classes: 0,
levels: 0,
multi: 0,
maxClass: "none",
maxClassPriority: 0,
maxClassLevels: 0,
maxClassPowerLevel: 0,
powersKnown: 0,
points: 0
};
// Keep track of the last seen caster in case we're in a single-caster situation.
let caster = null;
// Tabulate the total power-casting progression
const classes = this.data.items.filter(i => i.type === "class");
let priority = 0;
for ( let cls of classes ) {
const d = cls.data;
if ( d.powercasting === "none" ) continue;
const levels = d.levels;
const prog = d.powercasting;
// Accumulate levels
if ( prog !== "pact" ) {
caster = cls;
progression.total++;
}
switch (prog) {
case 'third': progression.slot += Math.floor(levels / 3); break;
case 'half': progression.slot += Math.floor(levels / 2); break;
case 'full': progression.slot += levels; break;
case 'artificer': progression.slot += Math.ceil(levels / 2); break;
case 'pact': progression.pact += levels; break;
}
case 'consular':
priority = 3;
forceProgression.levels += levels;
forceProgression.multi += (SW5E.powerMaxLevel['consular'][19]/9)*levels;
forceProgression.classes++;
// see if class controls high level forcecasting
if ((levels >= forceProgression.maxClassLevels) && (priority > forceProgression.maxClassPriority)){
forceProgression.maxClass = 'consular';
forceProgression.maxClassLevels = levels;
forceProgression.maxClassPriority = priority;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['consular'][Math.clamped((levels - 1), 0, 20)];
}
// calculate points and powers known
forceProgression.powersKnown += SW5E.powersKnown['consular'][Math.clamped((levels - 1), 0, 20)];
forceProgression.points += SW5E.powerPoints['consular'][Math.clamped((levels - 1), 0, 20)];
break;
case 'engineer':
priority = 2
techProgression.levels += levels;
techProgression.multi += (SW5E.powerMaxLevel['engineer'][19]/9)*levels;
techProgression.classes++;
// see if class controls high level techcasting
if ((levels >= techProgression.maxClassLevels) && (priority > techProgression.maxClassPriority)){
techProgression.maxClass = 'engineer';
techProgression.maxClassLevels = levels;
techProgression.maxClassPriority = priority;
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel['engineer'][Math.clamped((levels - 1), 0, 20)];
}
techProgression.powersKnown += SW5E.powersKnown['engineer'][Math.clamped((levels - 1), 0, 20)];
techProgression.points += SW5E.powerPoints['engineer'][Math.clamped((levels - 1), 0, 20)];
break;
case 'guardian':
priority = 1;
forceProgression.levels += levels;
forceProgression.multi += (SW5E.powerMaxLevel['guardian'][19]/9)*levels;
forceProgression.classes++;
// see if class controls high level forcecasting
if ((levels >= forceProgression.maxClassLevels) && (priority > forceProgression.maxClassPriority)){
forceProgression.maxClass = 'guardian';
forceProgression.maxClassLevels = levels;
forceProgression.maxClassPriority = priority;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['guardian'][Math.clamped((levels - 1), 0, 20)];
}
forceProgression.powersKnown += SW5E.powersKnown['guardian'][Math.clamped((levels - 1), 0, 20)];
forceProgression.points += SW5E.powerPoints['guardian'][Math.clamped((levels - 1), 0, 20)];
break;
case 'scout':
priority = 1;
techProgression.levels += levels;
techProgression.multi += (SW5E.powerMaxLevel['scout'][19]/9)*levels;
techProgression.classes++;
// see if class controls high level techcasting
if ((levels >= techProgression.maxClassLevels) && (priority > techProgression.maxClassPriority)){
techProgression.maxClass = 'scout';
techProgression.maxClassLevels = levels;
techProgression.maxClassPriority = priority;
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel['scout'][Math.clamped((levels - 1), 0, 20)];
}
techProgression.powersKnown += SW5E.powersKnown['scout'][Math.clamped((levels - 1), 0, 20)];
techProgression.points += SW5E.powerPoints['scout'][Math.clamped((levels - 1), 0, 20)];
break;
case 'sentinel':
priority = 2;
forceProgression.levels += levels;
forceProgression.multi += (SW5E.powerMaxLevel['sentinel'][19]/9)*levels;
forceProgression.classes++;
// see if class controls high level forcecasting
if ((levels >= forceProgression.maxClassLevels) && (priority > forceProgression.maxClassPriority)){
forceProgression.maxClass = 'sentinel';
forceProgression.maxClassLevels = levels;
forceProgression.maxClassPriority = priority;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['sentinel'][Math.clamped((levels - 1), 0, 20)];
}
forceProgression.powersKnown += SW5E.powersKnown['sentinel'][Math.clamped((levels - 1), 0, 20)];
forceProgression.points += SW5E.powerPoints['sentinel'][Math.clamped((levels - 1), 0, 20)];
break; }
}
// EXCEPTION: single-classed non-full progression rounds up, rather than down
const isSingleClass = (progression.total === 1) && (progression.slot > 0);
if (!isNPC && isSingleClass && ['half', 'third'].includes(caster.data.powercasting) ) {
const denom = caster.data.powercasting === 'third' ? 3 : 2;
progression.slot = Math.ceil(caster.data.levels / denom);
// EXCEPTION: multi-classed progression uses multi rounded down rather than levels
if (!isNPC && forceProgression.classes > 1) {
forceProgression.levels = Math.floor(forceProgression.multi);
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['multi'][forceProgression.levels - 1];
}
if (!isNPC && techProgression.classes > 1) {
techProgression.levels = Math.floor(techProgression.multi);
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel['multi'][techProgression.levels - 1];
}
// EXCEPTION: NPC with an explicit power-caster level
if (isNPC && actorData.data.details.powerLevel) {
progression.slot = actorData.data.details.powerLevel;
if (isNPC && actorData.data.details.powerForceLevel) {
forceProgression.levels = actorData.data.details.powerForceLevel;
actorData.data.attributes.force.level = forceProgression.levels;
forceProgression.maxClass = actorData.data.attributes.powercasting;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel[forceProgression.maxClass][Math.clamped((forceProgression.levels - 1), 0, 20)];
}
if (isNPC && actorData.data.details.powerTechLevel) {
techProgression.levels = actorData.data.details.powerTechLevel;
actorData.data.attributes.tech.level = techProgression.levels;
techProgression.maxClass = actorData.data.attributes.powercasting;
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel[techProgression.maxClass][Math.clamped((techProgression.levels - 1), 0, 20)];
}
// Look up the number of slots per level from the powerLimit table
let forcePowerLimit = Array.from(SW5E.powerLimit['none']);
for (let i = 0; i < (forceProgression.maxClassPowerLevel); i++) {
forcePowerLimit[i] = SW5E.powerLimit[forceProgression.maxClass][i];
}
// Look up the number of slots per level from the progression table
const levels = Math.clamped(progression.slot, 0, 20);
const slots = SW5E.SPELL_SLOT_TABLE[levels - 1] || [];
for ( let [n, lvl] of Object.entries(powers) ) {
let i = parseInt(n.slice(-1));
if ( Number.isNaN(i) ) continue;
if ( Number.isNumeric(lvl.override) ) lvl.max = Math.max(parseInt(lvl.override), 0);
else lvl.max = slots[i-1] || 0;
lvl.value = parseInt(lvl.value);
if ( Number.isNumeric(lvl.foverride) ) lvl.fmax = Math.max(parseInt(lvl.foverride), 0);
else lvl.fmax = forcePowerLimit[i-1] || 0;
if (isNPC){
lvl.fvalue = lvl.fmax;
}else{
lvl.fvalue = Math.min(parseInt(lvl.fvalue || lvl.value || lvl.fmax),lvl.fmax);
}
}
let techPowerLimit = Array.from(SW5E.powerLimit['none']);
for (let i = 0; i < (techProgression.maxClassPowerLevel); i++) {
techPowerLimit[i] = SW5E.powerLimit[techProgression.maxClass][i];
}
// Determine the Actor's pact magic level (if any)
let pl = Math.clamped(progression.pact, 0, 20);
powers.pact = powers.pact || {};
if ( (pl === 0) && isNPC && Number.isNumeric(powers.pact.override) ) pl = actorData.data.details.powerLevel;
for ( let [n, lvl] of Object.entries(powers) ) {
let i = parseInt(n.slice(-1));
if ( Number.isNaN(i) ) continue;
if ( Number.isNumeric(lvl.toverride) ) lvl.tmax = Math.max(parseInt(lvl.toverride), 0);
else lvl.tmax = techPowerLimit[i-1] || 0;
if (isNPC){
lvl.tvalue = lvl.tmax;
}else{
lvl.tvalue = Math.min(parseInt(lvl.tvalue || lvl.value || lvl.tmax),lvl.tmax);
}
}
// Determine the number of Warlock pact slots per level
if ( pl > 0) {
powers.pact.level = Math.ceil(Math.min(10, pl) / 2);
if ( Number.isNumeric(powers.pact.override) ) powers.pact.max = Math.max(parseInt(powers.pact.override), 1);
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.max = parseInt(powers.pact.override) || 0
powers.pact.level = powers.pact.max > 0 ? 1 : 0;
// Set Force and tech power for PC Actors
if (!isNPC && forceProgression.levels){
actorData.data.attributes.force.known.max = forceProgression.powersKnown;
actorData.data.attributes.force.points.max = forceProgression.points + Math.max(actorData.data.abilities.wis.mod,actorData.data.abilities.cha.mod);
actorData.data.attributes.force.level = forceProgression.levels;
}
if (!isNPC && techProgression.levels){
actorData.data.attributes.tech.known.max = techProgression.powersKnown;
actorData.data.attributes.tech.points.max = techProgression.points + actorData.data.abilities.int.mod;
actorData.data.attributes.tech.level = techProgression.levels;
}
// Tally Powers Known and check for migration first to avoid errors
let hasKnownPowers = actorData?.data?.attributes?.force?.known?.value !== undefined;
if ( hasKnownPowers ) {
const knownPowers = this.data.items.filter(i => i.type === "power");
let knownForcePowers = 0;
let knownTechPowers = 0;
for ( let knownPower of knownPowers ) {
const d = knownPower.data;
switch (knownPower.data.school){
case "lgt":
case "uni":
case "drk":{
knownForcePowers++;
break;
}
case "tec":{
knownTechPowers++;
break;
}
}
continue;
}
actorData.data.attributes.force.known.value = knownForcePowers;
actorData.data.attributes.tech.known.value = knownTechPowers;
}
}
@ -983,7 +1124,7 @@ export default class Actor5e extends Actor {
/* -------------------------------------------- */
/**
* Cause this Actor to take a Short Rest
* Cause this Actor to take a Short Rest and regain all Tech Points
* During a Short Rest resources and limited item uses may be recovered
* @param {boolean} dialog Present a dialog window which allows for rolling hit dice as part of the Short Rest
* @param {boolean} chat Summarize the results of the rest workflow as a chat message
@ -1016,10 +1157,14 @@ export default class Actor5e extends Actor {
}
}
// Note the change in HP and HD which occurred
// Note the change in HP and HD and TP which occurred
const dhd = this.data.data.attributes.hd - hd0;
const dhp = this.data.data.attributes.hp.value - hp0;
const dtp = this.data.data.attributes.tech.points.max - this.data.data.attributes.tech.points.value;
// Automatically Retore Tech Points
this.update({"data.attributes.tech.points.value": this.data.data.attributes.tech.points.max});
// Recover character resources
const updateData = {};
for ( let [k, r] of Object.entries(this.data.data.resources) ) {
@ -1028,11 +1173,6 @@ export default class Actor5e extends Actor {
}
}
// Recover pact slots.
const pact = this.data.data.powers.pact;
updateData['data.powers.pact.value'] = pact.override || pact.max;
await this.update(updateData);
// Recover item uses
const recovery = newDay ? ["sr", "day"] : ["sr"];
const items = this.items.filter(item => item.data.data.uses && recovery.includes(item.data.data.uses.per));
@ -1057,14 +1197,24 @@ export default class Actor5e extends Actor {
// Summarize the health effects
let srMessage = "SW5E.ShortRestResultShort";
if ((dhd !== 0) && (dhp !== 0)) srMessage = "SW5E.ShortRestResult";
if ((dhd !== 0) && (dhp !== 0)){
if (dtp !== 0){
srMessage = "SW5E.ShortRestResultWithTech";
}else{
srMessage = "SW5E.ShortRestResult";
}
}else{
if (dtp !== 0){
srMessage = "SW5E.ShortRestResultOnlyTech";
}
}
// Create a chat message
ChatMessage.create({
user: game.user._id,
speaker: {actor: this, alias: this.name},
flavor: restFlavor,
content: game.i18n.format(srMessage, {name: this.name, dice: -dhd, health: dhp})
content: game.i18n.format(srMessage, {name: this.name, dice: -dhd, health: dhp, tech: dtp})
});
}
@ -1072,6 +1222,7 @@ export default class Actor5e extends Actor {
return {
dhd: dhd,
dhp: dhp,
dtp: dtp,
updateData: updateData,
updateItems: updateItems,
newDay: newDay
@ -1081,7 +1232,7 @@ export default class Actor5e extends Actor {
/* -------------------------------------------- */
/**
* Take a long rest, recovering HP, HD, resources, and power slots
* Take a long rest, recovering HP, HD, resources, Force and Power points and power slots
* @param {boolean} dialog Present a confirmation dialog window whether or not to take a long rest
* @param {boolean} chat Summarize the results of the rest workflow as a chat message
* @param {boolean} newDay Whether the long rest carries over to a new day
@ -1099,12 +1250,20 @@ export default class Actor5e extends Actor {
}
}
// Recover hit points to full, and eliminate any existing temporary HP
// Recover hit, tech, and force points to full, and eliminate any existing temporary HP, TP, and FP
const dhp = data.attributes.hp.max - data.attributes.hp.value;
const dtp = data.attributes.tech.points.max - data.attributes.tech.points.value;
const dfp = data.attributes.force.points.max - data.attributes.force.points.value;
const updateData = {
"data.attributes.hp.value": data.attributes.hp.max,
"data.attributes.hp.temp": 0,
"data.attributes.hp.tempmax": 0
"data.attributes.hp.tempmax": 0,
"data.attributes.tech.points.value": data.attributes.tech.points.max,
"data.attributes.tech.points.temp": 0,
"data.attributes.tech.points.tempmax": 0,
"data.attributes.force.points.value": data.attributes.force.points.max,
"data.attributes.force.points.temp": 0,
"data.attributes.force.points.tempmax": 0
};
// Recover character resources
@ -1116,13 +1275,11 @@ export default class Actor5e extends Actor {
// Recover power slots
for ( let [k, v] of Object.entries(data.powers) ) {
updateData[`data.powers.${k}.value`] = Number.isNumeric(v.override) ? v.override : (v.max ?? 0);
updateData[`data.powers.${k}.fvalue`] = Number.isNumeric(v.foverride) ? v.foverride : (v.fmax ?? 0);
}
for ( let [k, v] of Object.entries(data.powers) ) {
updateData[`data.powers.${k}.tvalue`] = Number.isNumeric(v.toverride) ? v.toverride : (v.tmax ?? 0);
}
// Recover pact slots.
const pact = data.powers.pact;
updateData['data.powers.pact.value'] = pact.override || pact.max;
// Determine the number of hit dice which may be recovered
let recoverHD = Math.max(Math.floor(data.details.level / 2), 1);
let dhd = 0;
@ -1169,15 +1326,16 @@ export default class Actor5e extends Actor {
// Determine the chat message to display
if ( chat ) {
let lrMessage = "SW5E.LongRestResultShort";
if((dhp !== 0) && (dhd !== 0)) lrMessage = "SW5E.LongRestResult";
else if ((dhp !== 0) && (dhd === 0)) lrMessage = "SW5E.LongRestResultHitPoints";
else if ((dhp === 0) && (dhd !== 0)) lrMessage = "SW5E.LongRestResultHitDice";
let lrMessage = "SW5E.LongRestResult";
if (dhp !== 0) lrMessage += "HP";
if (dfp !== 0) lrMessage += "FP";
if (dtp !== 0) lrMessage += "TP";
if (dhd !== 0) lrMessage += "HD";
ChatMessage.create({
user: game.user._id,
speaker: {actor: this, alias: this.name},
flavor: restFlavor,
content: game.i18n.format(lrMessage, {name: this.name, health: dhp, dice: dhd})
content: game.i18n.format(lrMessage, {name: this.name, health: dhp, tech: dtp, force: dfp, dice: dhd})
});
}
@ -1185,6 +1343,8 @@ export default class Actor5e extends Actor {
return {
dhd: dhd,
dhp: dhp,
dtp: dtp,
dfp: dfp,
updateData: updateData,
updateItems: updateItems,
newDay: newDay

View file

@ -21,7 +21,8 @@ export default class ActorSheet5e extends ActorSheet {
*/
this._filters = {
inventory: new Set(),
powerbook: new Set(),
forcePowerbook: new Set(),
techPowerbook: new Set(),
features: new Set(),
effects: new Set()
};
@ -35,7 +36,8 @@ export default class ActorSheet5e extends ActorSheet {
scrollY: [
".inventory .group-list",
".features .group-list",
".powerbook .group-list",
".force-powerbook .group-list",
".tech-powerbook .group-list",
".effects .effects-list"
],
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
@ -220,7 +222,7 @@ export default class ActorSheet5e extends ActorSheet {
* @param {Array} powers The power data being prepared
* @private
*/
_preparePowerbook(data, powers) {
_preparePowerbook(data, powers, school) {
const owner = this.actor.owner;
const levels = data.data.powers;
const powerbook = {};
@ -229,7 +231,6 @@ export default class ActorSheet5e extends ActorSheet {
const sections = {
"atwill": -20,
"innate": -10,
"pact": 0.5
};
// Label power slot uses headers
@ -251,7 +252,7 @@ export default class ActorSheet5e extends ActorSheet {
uses: useLabels[i] || value || 0,
slots: useLabels[i] || max || 0,
override: override || 0,
dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode},
dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode, "school": school},
prop: sl
};
};
@ -273,19 +274,6 @@ export default class ActorSheet5e extends ActorSheet {
}
}
// 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]);
const l = levels.pact;
const config = CONFIG.SW5E.powerPreparationModes.pact;
registerSection("pact", sections.pact, config, {
prepMode: "pact",
value: l.value,
max: l.max,
override: l.override
});
}
// Iterate over every power item, adding powers to the powerbook by section
powers.forEach(power => {
const mode = power.data.preparation.mode || "prepared";

View file

@ -84,7 +84,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
};
// Partition items by category
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
let [items, forcepowers, techpowers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
// Item details
item.img = item.img || DEFAULT_TOKEN;
@ -112,23 +112,25 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
this._prepareItemToggleState(item);
// Classify items into types
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 ( item.type === "background" ) arr[7].push(item);
else if ( item.type === "fightingstyle" ) arr[8].push(item);
else if ( item.type === "fightingmastery" ) arr[9].push(item);
else if ( item.type === "lightsaberform" ) arr[10].push(item);
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[1].push(item);
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[2].push(item);
else if ( item.type === "feat" ) arr[3].push(item);
else if ( item.type === "class" ) arr[4].push(item);
else if ( item.type === "species" ) arr[5].push(item);
else if ( item.type === "archetype" ) arr[6].push(item);
else if ( item.type === "classfeature" ) arr[7].push(item);
else if ( item.type === "background" ) arr[8].push(item);
else if ( item.type === "fightingstyle" ) arr[9].push(item);
else if ( item.type === "fightingmastery" ) arr[10].push(item);
else if ( item.type === "lightsaberform" ) arr[11].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);
powers = this._filterItems(powers, this._filters.powerbook);
forcepowers = this._filterItems(forcepowers, this._filters.forcePowerbook);
techpowers = this._filterItems(techpowers, this._filters.techPowerbook);
feats = this._filterItems(feats, this._filters.features);
// Organize items
@ -140,10 +142,8 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
}
// Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...)
const powerbook = this._preparePowerbook(data, powers);
const nPrepared = powers.filter(s => {
return (s.data.level > 0) && (s.data.preparation.mode === "prepared") && s.data.preparation.prepared;
}).length;
const forcePowerbook = this._preparePowerbook(data, forcepowers, "uni");
const techPowerbook = this._preparePowerbook(data, techpowers, "tec");
// Organize Features
const features = {
@ -174,8 +174,8 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
// Assign and return
data.inventory = Object.values(inventory);
data.powerbook = powerbook;
data.preparedPowers = nPrepared;
data.forcePowerbook = forcePowerbook;
data.techPowerbook = techPowerbook;
data.features = Object.values(features);
}

View file

@ -42,24 +42,27 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
};
// Start by classifying items into groups for rendering
let [powers, other] = data.items.reduce((arr, item) => {
let [forcepowers, techpowers, other] = data.items.reduce((arr, item) => {
item.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
item.hasUses = item.data.uses && (item.data.uses.max > 0);
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0));
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type));
if ( item.type === "power" ) arr[0].push(item);
else arr[1].push(item);
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[0].push(item);
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[1].push(item);
else arr[2].push(item);
return arr;
}, [[], []]);
}, [[], [], []]);
// Apply item filters
powers = this._filterItems(powers, this._filters.powerbook);
forcepowers = this._filterItems(forcepowers, this._filters.forcePowerbook);
techpowers = this._filterItems(techpowers, this._filters.techPowerbook);
other = this._filterItems(other, this._filters.features);
// Organize Powerbook
const powerbook = this._preparePowerbook(data, powers);
const forcePowerbook = this._preparePowerbook(data, forcepowers, "uni");
const techPowerbook = this._preparePowerbook(data, techpowers, "tec");
// Organize Features
for ( let item of other ) {
@ -73,7 +76,8 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
// Assign and return
data.features = Object.values(features);
data.powerbook = powerbook;
data.forcePowerbook = forcePowerbook;
data.techPowerbook = techPowerbook;
}

View file

@ -100,32 +100,68 @@ export default class AbilityUseDialog extends Dialog {
// Determine the levels which are feasible
let lmax = 0;
const powerLevels = Array.fromRange(10).reduce((arr, i) => {
if ( i < lvl ) return arr;
const label = CONFIG.SW5E.powerLevels[i];
const l = actorData.powers["power"+i] || {max: 0, override: null};
let max = parseInt(l.override || l.max || 0);
let slots = Math.clamped(parseInt(l.value || 0), 0, max);
if ( max > 0 ) lmax = i;
arr.push({
level: i,
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
canCast: max > 0,
hasSlots: slots > 0
});
return arr;
}, []).filter(sl => sl.level <= lmax);
// If this character has pact slots, present them as an option for casting the power.
const pact = actorData.powers.pact;
if (pact.level >= lvl) {
powerLevels.push({
level: 'pact',
label: `${game.i18n.format('SW5E.PowerLevelPact', {level: pact.level, n: pact.value})}`,
canCast: true,
hasSlots: pact.value > 0
});
let points;
let powerType;
switch (itemData.school){
case "lgt":
case "uni":
case "drk": {
powerType = "force"
points = actorData.attributes.force.points.value + actorData.attributes.force.points.temp;
break;
}
case "tec": {
powerType = "tech"
points = actorData.attributes.tech.points.value + actorData.attributes.tech.points.temp;
break;
}
}
// eliminate point usage for innate casters
if (actorData.attributes.powercasting === 'innate') points = 999;
let powerLevels
if (powerType === "force"){
powerLevels = Array.fromRange(10).reduce((arr, i) => {
if ( i < lvl ) return arr;
const label = CONFIG.SW5E.powerLevels[i];
const l = actorData.powers["power"+i] || {fmax: 0, foverride: null};
let max = parseInt(l.foverride || l.fmax || 0);
let slots = Math.clamped(parseInt(l.fvalue || 0), 0, max);
if ( max > 0 ) lmax = i;
if ((max > 0) && (slots > 0) && (points > i)){
arr.push({
level: i,
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
canCast: max > 0,
hasSlots: slots > 0
});
}
return arr;
}, []).filter(sl => sl.level <= lmax);
}else if (powerType === "tech"){
powerLevels = Array.fromRange(10).reduce((arr, i) => {
if ( i < lvl ) return arr;
const label = CONFIG.SW5E.powerLevels[i];
const l = actorData.powers["power"+i] || {tmax: 0, toverride: null};
let max = parseInt(l.override || l.tmax || 0);
let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max);
if ( max > 0 ) lmax = i;
if ((max > 0) && (slots > 0) && (points > i)){
arr.push({
level: i,
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
canCast: max > 0,
hasSlots: slots > 0
});
}
return arr;
}, []).filter(sl => sl.level <= lmax);
}
const canCast = powerLevels.some(l => l.hasSlots);
if ( !canCast ) data.errors.push(game.i18n.format("SW5E.PowerCastNoSlots", {
level: CONFIG.SW5E.powerLevels[lvl],

View file

@ -74,7 +74,11 @@ export default class ActorSheetFlags extends BaseEntitySheet {
{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"}
{name: "data.bonuses.power.dc", label: "SW5E.BonusPowerDC"},
{name: "data.bonuses.power.forceLightDC", label: "SW5E.BonusForceLightPowerDC"},
{name: "data.bonuses.power.forceDarkDC", label: "SW5E.BonusForceDarkPowerDC"},
{name: "data.bonuses.power.forceUnivDC", label: "SW5E.BonusForceUnivPowerDC"},
{name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"}
];
for ( let b of bonuses ) {
b.value = getProperty(this.object._data, b.name) || "";

View file

@ -517,12 +517,77 @@ SW5E.powerPreparationModes = {
"innate": "SW5E.PowerPrepInnate"
};
SW5E.powerUpcastModes = ["always", "pact", "prepared"];
SW5E.powerUpcastModes = ["always", "prepared"];
/**
* The available choices for power progression for a character class
* @type {Object}
*/
SW5E.powerProgression = {
"none": "SW5E.PowerNone",
"full": "SW5E.PowerProgFull",
"artificer": "SW5E.PowerProgArt"
"consular": "SW5E.PowerProgCns",
"engineer": "SW5E.PowerProgEng",
"guardian": "SW5E.PowerProgGrd",
"scout": "SW5E.PowerProgSct",
"sentinel": "SW5E.PowerProgSnt"
};
/**
* The max number of known powers available to each class per level
*/
SW5E.powersKnown = {
"none": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
"consular": [9,11,13,15,17,19,21,23,25,26,28,29,31,32,34,35,37,38,39,40],
"engineer": [6,7,9,10,12,13,15,16,18,19,21,22,23,24,25,26,27,28,29,30],
"guardian": [5,7,9,10,12,13,14,15,17,18,19,20,22,23,24,25,27,28,29,30],
"scout": [0,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20,21,22,23],
"sentinel": [7,9,11,13,15,17,18,19,21,22,24,25,26,28,29,30,32,33,34,35]
};
/**
* The max number of powers cast for each power level per long rest
*/
SW5E.powerLimit = {
"none": [0,0,0,0,0,0,0,0,0],
"consular": [1000,1000,1000,1000,1000,1,1,1,1],
"engineer": [1000,1000,1000,1000,1000,1,1,1,1],
"guardian": [1000,1000,1000,1000,1,0,0,0,0],
"scout": [1000,1000,1000,1,1,0,0,0,0],
"sentinel": [1000,1000,1000,1000,1,1,1,0,0],
"innate": [1000,1000,1000,1000,1000,1000,1000,1000,1000],
"dual": [1000,1000,1000,1000,1000,1,1,1,1]
};
/**
* The max level of a known/overpowered power available to each class per level
*/
SW5E.powerMaxLevel = {
"none": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
"consular": [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9,9],
"engineer": [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9,9],
"guardian": [1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5],
"scout": [0,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5],
"sentinel": [1,1,2,2,2,3,3,3,4,4,5,5,5,6,6,6,7,7,7,7],
"multi": [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9,9],
"innate": [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9,9],
"dual": [1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9,9]
};
/**
* The number of base force/tech points available to each class per level
*/
SW5E.powerPoints = {
"none": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
"consular": [4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80],
"engineer": [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40],
"guardian": [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40],
"scout": [0,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
"sentinel": [3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60]
};
/* -------------------------------------------- */
@ -631,36 +696,6 @@ SW5E.powerLevels = {
9: "SW5E.PowerLevel9"
};
/**
* Define the standard slot progression by character level.
* The entries of this array represent the power slot progression for a full power-caster.
* @type {Array[]}
*/
SW5E.SPELL_SLOT_TABLE = [
[2],
[3],
[4, 2],
[4, 3],
[4, 3, 2],
[4, 3, 3],
[4, 3, 3, 1],
[4, 3, 3, 2],
[4, 3, 3, 3, 1],
[4, 3, 3, 3, 2],
[4, 3, 3, 3, 2, 1],
[4, 3, 3, 3, 2, 1],
[4, 3, 3, 3, 2, 1, 1],
[4, 3, 3, 3, 2, 1, 1],
[4, 3, 3, 3, 2, 1, 1, 1],
[4, 3, 3, 3, 2, 1, 1, 1],
[4, 3, 3, 3, 2, 1, 1, 1, 1],
[4, 3, 3, 3, 3, 1, 1, 1, 1],
[4, 3, 3, 3, 3, 2, 1, 1, 1],
[4, 3, 3, 3, 3, 2, 2, 1, 1]
];
/* -------------------------------------------- */
// Polymorph options.
SW5E.polymorphSettings = {
keepPhysical: 'SW5E.PolymorphKeepPhysical',
@ -1207,4 +1242,4 @@ SW5E.characterFlags = {
};
// Configure allowed status flags
SW5E.allowedActorFlags = ["isPolymorphed", "originalActor"].concat(Object.keys(SW5E.characterFlags));
SW5E.allowedActorFlags = ["isPolymorphed", "originalActor", "dataVersion"].concat(Object.keys(SW5E.characterFlags));

View file

@ -25,8 +25,17 @@ export default class Item5e extends Item {
else if (this.actor) {
const actorData = this.actor.data.data;
// Powers - Use Actor powercasting modifier
if (this.data.type === "power") return actorData.attributes.powercasting || "int";
// Powers - Use Actor powercasting modifier based on power school
if (this.data.type === "power") {
switch (this.data.data.school) {
case "lgt": return "wis";
case "uni": return (actorData.abilities["wis"].mod >= actorData.abilities["cha"].mod) ? "wis" : "cha";
case "drk": return "cha";
case "tec": return "int";
}
return "none";
}
// Tools - default to Intelligence
else if (this.data.type === "tool") return "int";
@ -291,7 +300,24 @@ export default class Item5e extends Item {
// Actor power-DC based scaling
if ( save.scaling === "power" ) {
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.powerdc") : null;
switch (this.data.data.school) {
case "lgt": {
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.powerForceLightDC") : null;
break;
}
case "uni": {
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.powerForceUnivDC") : null;
break;
}
case "drk": {
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.powerForceDarkDC") : null;
break;
}
case "tec": {
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.powerTechDC") : null;
break;
}
}
}
// Ability-score based scaling
@ -394,6 +420,7 @@ export default class Item5e extends Item {
const recharge = id.recharge || {}; // Recharge mechanic
const uses = id?.uses ?? {}; // Limited uses
const isPower = this.type === "power"; // Does the item require a power slot?
// TODO: Possibly Mod this to not consume slots based on class?
const requirePowerSlot = isPower && (id.level > 0) && CONFIG.SW5E.powerUpcastModes.includes(id.preparation.mode);
// Define follow-up actions resulting from the item usage
@ -404,11 +431,12 @@ export default class Item5e extends Item {
let consumeUsage = !!uses.per; // Consume limited uses
let consumeQuantity = uses.autoDestroy; // Consume quantity of the item in lieu of uses
// Display a configuration dialog to customize the usage
// Display a configuration dialog to customize the usage
const needsConfiguration = createMeasuredTemplate || consumeRecharge || consumeResource || consumePowerSlot || consumeUsage;
if (configureDialog && needsConfiguration) {
const configuration = await AbilityUseDialog.create(this);
if (!configuration) return;
// Determine consumption preferences
createMeasuredTemplate = Boolean(configuration.placeTemplate);
@ -420,18 +448,20 @@ export default class Item5e extends Item {
// Handle power upcasting
if ( requirePowerSlot ) {
const slotLevel = configuration.level;
const powerLevel = slotLevel === "pact" ? actor.data.data.powers.pact.level : parseInt(slotLevel);
const powerLevel = parseInt(slotLevel);
if (powerLevel !== id.level) {
const upcastData = mergeObject(this.data, {"data.level": powerLevel}, {inplace: false});
item = this.constructor.createOwned(upcastData, actor); // Replace the item with an upcast version
}
if ( consumePowerSlot ) consumePowerSlot = slotLevel === "pact" ? "pact" : `power${powerLevel}`;
if ( consumePowerSlot ) consumePowerSlot = `power${powerLevel}`;
}
}
// Determine whether the item can be used by testing for resource consumption
const usage = item._getUsageUpdates({consumeRecharge, consumeResource, consumePowerSlot, consumeUsage, consumeQuantity});
if ( !usage ) return;
const {actorUpdates, itemUpdates, resourceUpdates} = usage;
// Commit pending data updates
@ -490,17 +520,53 @@ export default class Item5e extends Item {
if ( canConsume === false ) return false;
}
// Consume Power Slots
// Consume Power Slots and Force/Tech Points
if ( consumePowerSlot ) {
const level = this.actor?.data.data.powers[consumePowerSlot];
const powers = Number(level?.value ?? 0);
if ( powers === 0 ) {
const label = game.i18n.localize(consumePowerSlot === "pact" ? "SW5E.PowerProgPact" : `SW5E.PowerLevel${id.level}`);
ui.notifications.warn(game.i18n.format("SW5E.PowerCastNoSlots", {name: this.name, level: label}));
return false;
const fp = this.actor.data.data.attributes.force.points;
const tp = this.actor.data.data.attributes.tech.points;
const powerCost = id.level + 1;
const innatePower = this.actor.data.data.attributes.powercasting === 'innate';
if (!innatePower){
switch (id.school){
case "lgt":
case "uni":
case "drk": {
const powers = Number(level?.fvalue ?? 0);
if ( powers === 0 ) {
const label = game.i18n.localize(`SW5E.PowerLevel${id.level}`);
ui.notifications.warn(game.i18n.format("SW5E.PowerCastNoSlots", {name: this.name, level: label}));
return false;
}
actorUpdates[`data.powers.${consumePowerSlot}.fvalue`] = Math.max(powers - 1, 0);
if (fp.temp >= powerCost) {
actorUpdates["data.attributes.force.points.temp"] = fp.temp - powerCost;
}else{
actorUpdates["data.attributes.force.points.value"] = fp.value + fp.temp - powerCost;
actorUpdates["data.attributes.force.points.temp"] = 0;
}
break;
}
case "tec": {
const powers = Number(level?.tvalue ?? 0);
if ( powers === 0 ) {
const label = game.i18n.localize(`SW5E.PowerLevel${id.level}`);
ui.notifications.warn(game.i18n.format("SW5E.PowerCastNoSlots", {name: this.name, level: label}));
return false;
}
actorUpdates[`data.powers.${consumePowerSlot}.tvalue`] = Math.max(powers - 1, 0);
if (tp.temp >= powerCost) {
actorUpdates["data.attributes.tech.points.temp"] = tp.temp - powerCost;
}else{
actorUpdates["data.attributes.tech.points.value"] = tp.value + tp.temp - powerCost;
actorUpdates["data.attributes.tech.points.temp"] = 0;
}
break;
}
}
}
actorUpdates[`data.powers.${consumePowerSlot}.value`] = Math.max(powers - 1, 0);
}
// Consume Limited Usage
if ( consumeUsage ) {
@ -772,7 +838,7 @@ export default class Item5e extends Item {
/* -------------------------------------------- */
/**
* Prepare chat card data for tool type items
* Prepare chat card data for loot type items
* @private
*/
_lootChatData(data, labels, props) {

View file

@ -1,5 +1,5 @@
import TraitSelector from "../apps/trait-selector.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../effects.js";
import { onManageActiveEffect, prepareActiveEffectCategories } from "../effects.js";
/**
* Override and extend the core ItemSheet implementation to handle specific item types
@ -10,8 +10,8 @@ export default class ItemSheet5e extends ItemSheet {
super(...args);
// Expand the default size of the class sheet
if ( this.object.data.type === "class" ) {
this.options.width = this.position.width = 600;
if (this.object.data.type === "class") {
this.options.width = this.position.width = 600;
this.options.height = this.position.height = 680;
}
}
@ -19,14 +19,14 @@ export default class ItemSheet5e extends ItemSheet {
/* -------------------------------------------- */
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
width: 560,
height: 400,
classes: ["sw5e", "sheet", "item"],
resizable: true,
scrollY: [".tab.details"],
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description" }]
});
}
@ -55,17 +55,17 @@ export default class ItemSheet5e extends ItemSheet {
// Potential consumption targets
data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item);
// Action Details
// Action Detail
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);
// Original maximum uses formula
if ( this.item._data.data?.uses?.max ) data.data.uses.max = this.item._data.data.uses.max;
if (this.item._data.data?.uses?.max) data.data.uses.max = this.item._data.data.uses.max;
// Vehicles
data.isCrewed = data.item.data.activation?.type === 'crew';
data.isCrewed = data.item.data.activation?.type === "crew";
data.isMountable = this._isItemMountable(data.item);
// Prepare Active Effects
@ -83,22 +83,25 @@ export default class ItemSheet5e extends ItemSheet {
*/
_getItemConsumptionTargets(item) {
const consume = item.data.consume || {};
if ( !consume.type ) return [];
if (!consume.type) return [];
const actor = this.item.actor;
if ( !actor ) return {};
if (!actor) return {};
// Ammunition
if ( consume.type === "ammo" ) {
return actor.itemTypes.consumable.reduce((ammo, i) => {
if ( i.data.data.consumableType === "ammo" ) {
ammo[i.id] = `${i.name} (${i.data.data.quantity})`;
}
return ammo;
}, {[item._id]: `${item.name} (${item.data.quantity})`});
if (consume.type === "ammo") {
return actor.itemTypes.consumable.reduce(
(ammo, i) => {
if (i.data.data.consumableType === "ammo") {
ammo[i.id] = `${i.name} (${i.data.data.quantity})`;
}
return ammo;
},
{ [item._id]: `${item.name} (${item.data.quantity})` }
);
}
// Attributes
else if ( consume.type === "attribute" ) {
else if (consume.type === "attribute") {
const attributes = Object.values(CombatTrackerConfig.prototype.getAttributeChoices())[0]; // Bit of a hack
return attributes.reduce((obj, a) => {
obj[a] = a;
@ -107,9 +110,9 @@ export default class ItemSheet5e extends ItemSheet {
}
// Materials
else if ( consume.type === "material" ) {
else if (consume.type === "material") {
return actor.items.reduce((obj, i) => {
if ( ["consumable", "loot"].includes(i.data.type) && !i.data.data.activation ) {
if (["consumable", "loot"].includes(i.data.type) && !i.data.data.activation) {
obj[i.id] = `${i.name} (${i.data.data.quantity})`;
}
return obj;
@ -117,25 +120,24 @@ export default class ItemSheet5e extends ItemSheet {
}
// Charges
else if ( consume.type === "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" ?
` (${game.i18n.format("SW5E.AbilityUseChargesLabel", {value: uses.value})})` :
` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {max: uses.max, per: uses.per})})`;
if (uses.per && uses.max) {
const label =
uses.per === "charges"
? ` (${game.i18n.format("SW5E.AbilityUseChargesLabel", { value: uses.value })})`
: ` (${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")})`;
if (recharge.value) obj[i.id] = `${i.name} (${game.i18n.format("SW5E.Recharge")})`;
return obj;
}, {})
}
else return {};
}, {});
} else return {};
}
/* -------------------------------------------- */
@ -146,13 +148,11 @@ export default class ItemSheet5e extends ItemSheet {
* @private
*/
_getItemStatus(item) {
if ( item.type === "power" ) {
if (item.type === "power") {
return CONFIG.SW5E.powerPreparationModes[item.data.preparation];
}
else if ( ["weapon", "equipment"].includes(item.type) ) {
} else if (["weapon", "equipment"].includes(item.type)) {
return game.i18n.localize(item.data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped");
}
else if ( item.type === "tool" ) {
} else if (item.type === "tool") {
return game.i18n.localize(item.data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient");
}
}
@ -168,67 +168,50 @@ export default class ItemSheet5e extends ItemSheet {
const props = [];
const labels = this.item.labels;
if ( item.type === "weapon" ) {
props.push(...Object.entries(item.data.properties)
.filter(e => e[1] === true)
.map(e => CONFIG.SW5E.weaponProperties[e[0]]));
}
else if ( item.type === "power" ) {
if (item.type === "weapon") {
props.push(
...Object.entries(item.data.properties)
.filter((e) => e[1] === true)
.map((e) => CONFIG.SW5E.weaponProperties[e[0]])
);
} else if (item.type === "power") {
props.push(
labels.components,
labels.materials,
item.data.components.concentration ? game.i18n.localize("SW5E.Concentration") : null,
item.data.components.ritual ? game.i18n.localize("SW5E.Ritual") : null
)
}
else if ( item.type === "equipment" ) {
);
} else if (item.type === "equipment") {
props.push(CONFIG.SW5E.equipmentTypes[item.data.armor.type]);
props.push(labels.armor);
} else if (item.type === "feat") {
props.push(labels.featType);
} else if (item.type === "species") {
//props.push(labels.species);
} else if (item.type === "archetype") {
//props.push(labels.archetype);
} else if (item.type === "background") {
//props.push(labels.background);
} else if (item.type === "classfeature") {
//props.push(labels.classfeature);
} else if (item.type === "fightingmastery") {
//props.push(labels.fightingmastery);
} else if (item.type === "fightingstyle") {
//props.push(labels.fightingstyle);
} else if (item.type === "lightsaberform") {
//props.push(labels.lightsaberform);
}
else if ( item.type === "feat" ) {
props.push(labels.featType);
}
else if ( item.type === "species" ) {
//props.push(labels.species);
}
else if ( item.type === "archetype" ) {
//props.push(labels.archetype);
}
else if ( item.type === "background" ) {
//props.push(labels.background);
}
else if ( item.type === "classfeature" ) {
//props.push(labels.classfeature);
}
else if ( item.type === "fightingmastery" ) {
//props.push(labels.fightingmastery);
}
else if ( item.type === "fightingstyle" ) {
//props.push(labels.fightingstyle);
}
else if ( item.type === "lightsaberform" ) {
//props.push(labels.lightsaberform);
}
// Action type
if ( item.data.actionType ) {
if (item.data.actionType) {
props.push(CONFIG.SW5E.itemActionTypes[item.data.actionType]);
}
// Action usage
if ( (item.type !== "weapon") && item.data.activation && !isObjectEmpty(item.data.activation) ) {
props.push(
labels.activation,
labels.range,
labels.target,
labels.duration
)
if (item.type !== "weapon" && item.data.activation && !isObjectEmpty(item.data.activation)) {
props.push(labels.activation, labels.range, labels.target, labels.duration);
}
return props.filter(p => !!p);
return props.filter((p) => !!p);
}
/* -------------------------------------------- */
@ -243,36 +226,37 @@ export default class ItemSheet5e extends ItemSheet {
*/
_isItemMountable(item) {
const data = item.data;
return (item.type === 'weapon' && data.weaponType === 'siege')
|| (item.type === 'equipment' && data.armor.type === 'vehicle');
return (
(item.type === "weapon" && data.weaponType === "siege") ||
(item.type === "equipment" && data.armor.type === "vehicle")
);
}
/* -------------------------------------------- */
/** @override */
setPosition(position={}) {
if ( !(this._minimized || position.height) ) {
position.height = (this._tabs[0].active === "details") ? "auto" : this.options.height;
setPosition(position = {}) {
if (!(this._minimized || position.height)) {
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
}
return super.setPosition(position);
}
/* -------------------------------------------- */
/* Form Submission */
/* -------------------------------------------- */
/* -------------------------------------------- */
/** @override */
_getSubmitData(updateData={}) {
_getSubmitData(updateData = {}) {
// Create the expanded update data object
const fd = new FormDataExtended(this.form, {editors: this.editors});
const fd = new FormDataExtended(this.form, { editors: this.editors });
let data = fd.toObject();
if ( updateData ) data = mergeObject(data, updateData);
if (updateData) data = mergeObject(data, updateData);
else data = expandObject(data);
// Handle Damage array
const damage = data.data?.damage;
if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
if (damage) damage.parts = Object.values(damage?.parts || {}).map((d) => [d[0] || "", d[1] || ""]);
// Return the flattened submission data
return flattenObject(data);
@ -283,12 +267,15 @@ export default class ItemSheet5e extends ItemSheet {
/** @override */
activateListeners(html) {
super.activateListeners(html);
if ( this.isEditable ) {
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)
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);
});
}
}
@ -306,19 +293,19 @@ export default class ItemSheet5e extends ItemSheet {
const a = event.currentTarget;
// Add new damage component
if ( a.classList.contains("add-damage") ) {
await this._onSubmit(event); // Submit any unsaved changes
if (a.classList.contains("add-damage")) {
await this._onSubmit(event); // Submit any unsaved changes
const damage = this.item.data.data.damage;
return this.item.update({"data.damage.parts": damage.parts.concat([["", ""]])});
return this.item.update({ "data.damage.parts": damage.parts.concat([["", ""]]) });
}
// Remove a damage component
if ( a.classList.contains("delete-damage") ) {
await this._onSubmit(event); // Submit any unsaved changes
if (a.classList.contains("delete-damage")) {
await this._onSubmit(event); // Submit any unsaved changes
const li = a.closest(".damage-part");
const damage = duplicate(this.item.data.data.damage);
damage.parts.splice(Number(li.dataset.damagePart), 1);
return this.item.update({"data.damage.parts": damage.parts});
return this.item.update({ "data.damage.parts": damage.parts });
}
}
@ -341,19 +328,19 @@ export default class ItemSheet5e extends ItemSheet {
name: a.dataset.target,
title: label.innerText,
choices: Object.entries(CONFIG.SW5E.skills).reduce((obj, e) => {
if ( choices.includes(e[0] ) ) obj[e[0]] = e[1];
if (choices.includes(e[0])) obj[e[0]] = e[1];
return obj;
}, {}),
minimum: skills.number,
maximum: skills.number
}).render(true)
}).render(true);
}
/* -------------------------------------------- */
/** @override */
async _onSubmit(...args) {
if ( this._tabs[0].active === "details" ) this.position.height = "auto";
if (this._tabs[0].active === "details") this.position.height = "auto";
await super._onSubmit(...args);
}
}

View file

@ -131,27 +131,37 @@ export const migrateActorData = function(actor) {
_migrateActorSenses(actor, updateData);
// Migrate Owned Items
if ( !actor.items ) return updateData;
let hasItemUpdates = false;
const items = actor.items.map(i => {
if ( !!actor.items ) {
let hasItemUpdates = false;
const items = actor.items.map(i => {
// Migrate the Owned Item
let itemUpdate = migrateItemData(i);
// Migrate the Owned Item
let itemUpdate = migrateItemData(i);
// Prepared, Equipped, and Proficient for NPC actors
if ( actor.type === "npc" ) {
if (getProperty(i.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
if (getProperty(i.data, "equipped") === false) itemUpdate["data.equipped"] = true;
if (getProperty(i.data, "proficient") === false) itemUpdate["data.proficient"] = true;
}
// Prepared, Equipped, and Proficient for NPC actors
if ( actor.type === "npc" ) {
if (getProperty(i.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
if (getProperty(i.data, "equipped") === false) itemUpdate["data.equipped"] = true;
if (getProperty(i.data, "proficient") === false) itemUpdate["data.proficient"] = true;
}
// Update the Owned Item
if ( !isObjectEmpty(itemUpdate) ) {
hasItemUpdates = true;
return mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false});
} else return i;
});
if ( hasItemUpdates ) updateData.items = items;
// Update the Owned Item
if ( !isObjectEmpty(itemUpdate) ) {
hasItemUpdates = true;
return mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false});
} else return i;
});
if ( hasItemUpdates ) updateData.items = items;
}
// Update NPC data with new datamodel information
if (actor.type === "npc") {
_updateNPCData(actor);
}
// migrate powers last since it relies on item classes being migrated first.
_migrateActorPowers(actor, updateData);
return updateData;
};
@ -191,6 +201,7 @@ function cleanActorData(actorData) {
*/
export const migrateItemData = function(item) {
const updateData = {};
_migrateItemClassPowerCasting(item, updateData)
_migrateItemAttunement(item, updateData);
return updateData;
};
@ -228,6 +239,69 @@ export const migrateSceneData = function(scene) {
/* Low level migration utilities
/* -------------------------------------------- */
/* -------------------------------------------- */
/**
* Update an NPC Actor's data based on compendium
* @param {Object} actor The data object for an Actor
* @return {Object} The updated Actor
*/
function _updateNPCData(actor) {
let actorData = actor.data;
const updateData = {};
// check for flag.core
const hasSource = actor?.flags?.core?.sourceId !== undefined;
if (!hasSource) return actor;
// shortcut out if dataVersion flag is set to 1.2.4
const sourceId = actor.flags.core.sourceId;
const coreSource = sourceId.substr(0,sourceId.length-17);
const core_id = sourceId.substr(sourceId.length-16,16);
if (coreSource === "Compendium.sw5e.monsters"){
game.packs.get("sw5e.monsters").getEntity(core_id).then(monster => {
const monsterData = monster.data.data;
// copy movement[], senses[], powercasting, force[], tech[], powerForceLevel, powerTechLevel
updateData["data.attributes.movement"] = monsterData.attributes.movement;
updateData["data.attributes.senses"] = monsterData.attributes.senses;
updateData["data.attributes.powercasting"] = monsterData.attributes.powercasting;
updateData["data.attributes.force"] = monsterData.attributes.force;
updateData["data.attributes.tech"] = monsterData.attributes.tech;
updateData["data.details.powerForceLevel"] = monsterData.details.powerForceLevel;
updateData["data.details.powerTechLevel"] = monsterData.details.powerTechLevel;
// push missing powers onto actor
let newPowers = [];
for ( let i of monster.items ) {
const itemData = i.data;
if ( itemData.type === "power" ) {
const itemCompendium_id = itemData.flags?.core?.sourceId.split(".").slice(-1)[0];
let hasPower = !!actor.items.find(item => item.flags?.core?.sourceId.split(".").slice(-1)[0] === itemCompendium_id);
if (!hasPower) {
// Clone power to new object. Don't know if it is technically needed, but seems to prevent some weirdness.
const newPower = JSON.parse(JSON.stringify(itemData));
newPowers.push(newPower);
}
}
}
const liveActor = game.actors.get(actor._id);
liveActor.createEmbeddedEntity("OwnedItem", newPowers);
// let updateActor = await actor.createOwnedItem(newPowers);
// set flag to check to see if migration has been done so we don't do it again.
liveActor.setFlag("sw5e", "dataVersion", "1.2.4");
})
}
//merge object
actorData = mergeObject(actorData, updateData);
// Return the scrubbed data
return actor;
}
/**
* Migrate the actor speed string to movement object
* @private
@ -255,6 +329,67 @@ function _migrateActorMovement(actorData, updateData) {
/* -------------------------------------------- */
/**
* Migrate the actor speed string to movement object
* @private
*/
function _migrateActorPowers(actorData, updateData) {
const ad = actorData.data;
// If new Force & Tech data is not present, create it
let hasNewAttrib = ad?.attributes?.force?.level !== undefined;
if ( !hasNewAttrib ) {
updateData["data.attributes.force.known.value"] = 0;
updateData["data.attributes.force.known.max"] = 0;
updateData["data.attributes.force.points.value"] = 0;
updateData["data.attributes.force.points.min"] = 0;
updateData["data.attributes.force.points.max"] = 0;
updateData["data.attributes.force.points.temp"] = 0;
updateData["data.attributes.force.points.tempmax"] = 0;
updateData["data.attributes.force.level"] = 0;
updateData["data.attributes.tech.known.value"] = 0;
updateData["data.attributes.tech.known.max"] = 0;
updateData["data.attributes.tech.points.value"] = 0;
updateData["data.attributes.tech.points.min"] = 0;
updateData["data.attributes.tech.points.max"] = 0;
updateData["data.attributes.tech.points.temp"] = 0;
updateData["data.attributes.tech.points.tempmax"] = 0;
updateData["data.attributes.tech.level"] = 0;
}
// If new Power F/T split data is not present, create it
const hasNewLimit = ad?.powers?.power1?.foverride !== undefined;
if ( !hasNewLimit ) {
for (let i = 1; i <= 9; i++) {
// add new
updateData["data.powers.power" + i + ".fvalue"] = getProperty(ad.powers,"power" + i + ".value");
updateData["data.powers.power" + i + ".fmax"] = getProperty(ad.powers,"power" + i + ".max");
updateData["data.powers.power" + i + ".foverride"] = null;
updateData["data.powers.power" + i + ".tvalue"] = getProperty(ad.powers,"power" + i + ".value");
updateData["data.powers.power" + i + ".tmax"] = getProperty(ad.powers,"power" + i + ".max");
updateData["data.powers.power" + i + ".toverride"] = null;
//remove old
updateData["data.powers.power" + i + ".-=value"] = null;
updateData["data.powers.power" + i + ".-=override"] = null;
}
}
// If new Bonus Power DC data is not present, create it
const hasNewBonus = ad?.bonuses?.power?.forceLightDC !== undefined;
if ( !hasNewBonus ) {
updateData["data.bonuses.power.forceLightDC"] = "";
updateData["data.bonuses.power.forceDarkDC"] = "";
updateData["data.bonuses.power.forceUnivDC"] = "";
updateData["data.bonuses.power.techDC"] = "";
}
// Remove the Power DC Bonus
updateData["data.bonuses.power.-=dc"] = null;
return updateData
}
/* -------------------------------------------- */
/**
* Migrate the actor traits.senses string to attributes.senses object
* @private
@ -290,6 +425,35 @@ function _migrateActorSenses(actor, updateData) {
return updateData;
}
/* -------------------------------------------- */
/**
* @private
*/
function _migrateItemClassPowerCasting(item, updateData) {
if (item.type === "class"){
switch (item.name){
case "Consular":
updateData["data.powercasting"] = "consular";
break;
case "Engineer":
updateData["data.powercasting"] = "engineer";
break;
case "Guardian":
updateData["data.powercasting"] = "guardian";
break;
case "Scout":
updateData["data.powercasting"] = "scout";
break;
case "Sentinel":
updateData["data.powercasting"] = "sentinel";
break;
}
}
return updateData;
}
/* -------------------------------------------- */
/**

View file

@ -21,7 +21,8 @@ export const preloadHandlebarsTemplates = async function() {
"systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-features.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-powerbook.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-force-powerbook.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-tech-powerbook.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show more