Merge pull request #172 from unrealkakeman89/Develop

Develop
This commit is contained in:
CK 2021-03-16 10:15:16 -04:00 committed by GitHub
commit fee77e2172
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
213 changed files with 5976 additions and 3824 deletions

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

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

File diff suppressed because it is too large Load diff

1027
lang/fr.json Normal file

File diff suppressed because it is too large Load diff

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 {

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

@ -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.attributes.powercasting !== "none" ? 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',
@ -858,353 +893,353 @@ SW5E.classFeatures = ClassFeatures;
// Configure Optional Character Flags
SW5E.characterFlags = {
"adaptiveResilience": {
name: "SW5E.FlagsAdaptiveResilience",
hint: "Prolongued use of technology allows members of your species to readily adapt to its effects. You have advantage on Strength and Constitution saving throws against tech powers.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsAdaptiveResilience",
hint: "SW5E.FlagsAdaptiveResilienceHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"aggressive": {
name: "SW5E.FlagsAggressive",
hint: "As a bonus action, you can move up to your speed toward an enemy of your choice that you can see or hear. You must end this move closer to the enemy than you started.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsAggressive",
hint: "SW5E.FlagsAggressiveHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"amphibious": {
name: "SW5E.FlagsAmphibious",
hint: "You can breathe air and water.",
section: "Species Traits",
name: "SW5E.FlagsAmphibious",
hint: "SW5E.FlagsAmphibiousHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"armorIntegration": {
name: "SW5E.FlagsArmorIntegration",
hint: "You cannot wear armor, but you can have the armor professionally integrated into your chassis over the course of a long rest. This work must be done by someone proficient with astrotechs implements. You must be proficient in armor in order to have it integrated.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsArmorIntegration",
hint: "SW5E.FlagsArmorIntegrationHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"businessSavvy": {
name: "SW5E.FlagsBusinessSavvy",
hint: "Whenever you make a Charisma (Persuasion) check involving haggling you are considered to have expertise in the Persuasion skill.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsBusinessSavvy",
hint: "SW5E.FlagsBusinessSavvyHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"cannibalize": {
name: "SW5E.FlagsCannibalize",
hint: "If you spend at least 1 minute devouring the corpse of a beast or humanoid, you gain temporary hit points equal to your Constitution modifier. Once you've used this feature, you must complete a short or long rest before you can use it again.",
section: "Species Traits",
name: "SW5E.FlagsCannibalize",
hint: "SW5E.FlagsCannibalizeHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"closedMind": {
name: "SW5E.FlagsClosedMind",
hint: "Members of your species have a natural attunement to the Force, which makes them resistant to its powers. You have advantage on Wisdom and Charisma saving throws against force powers.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsClosedMind",
hint: "SW5E.FlagsClosedMindHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"crudeWeaponSpecialists": {
name: "SW5E.FlagsCrudeWeaponSpecialists",
hint: "Members of your species are used to making do with less. You can spend 1 hour, which you can do over the course of a short rest, crafting a weapon out of loose materials. You can craft any simple kinetic weapon, but the weapons damage suffers a -1 penalty.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsCrudeWeaponSpecialists",
hint: "SW5E.FlagsCrudeWeaponSpecialistsHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"defiant": {
name: "SW5E.FlagsDefiant",
hint: "Members of your species are known to be stubborn and often refuse to give up, even against the worst odds. When you or a creature you can see that can see and understand you makes an ability check, attack roll, or saving throw, you can roll a d4 and add it to their roll (no action required). You can use this before or after the roll, but before the GM determines the rolls outcome. Once youve used this feature, you must complete a short or long rest before you can use it again.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsDefiant",
hint: "SW5E.FlagsDefiantHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"detailOriented": {
name: "SW5E.FlagsDetailOriented",
hint: "You have advantage on Intelligence (Investigation) checks within 5 feet.",
section: "Species Traits",
hint: "SW5E.FlagsDetailOrientedHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"enthrallingPheromones": {
name: "SW5E.FlagsEnthrallingPheromones",
hint: "You can use your pheromones to influence individuals of both sexes. Whenever you roll a 1 on a Charisma (Persuasion) check, you can reroll the die and must use the new roll. Additionally, once per short or long rest, you can treat a d20 roll of 9 or lower on a Charisma check as a 10. This feature has no effect on droids or constructs.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsEnthrallingPheromones",
hint: "SW5E.FlagsEnthrallingPheromonesHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"extraArms": {
name: "SW5E.FlagsExtraArms",
hint: "You possess more than two arms, which you can use independently of one another. You can only gain the benefit of items held by two of your arms at any given time, and once per round you can switch which arms you are benefiting from (no action required).",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsExtraArms",
hint: "SW5E.FlagsExtraArmsHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"forceContention": {
name: "SW5E.FlagsForceContention",
hint: "Due to their unique physiology, members of your species exhibit a hardiness that allows them to overcome use of the Force. You have advantage on Strength and Constitution saving throws against force powers.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsForceContention",
hint: "SW5E.FlagsForceContentionHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"forceInsensitive": {
name: "SW5E.FlagsForceInsensitive",
hint: "While droids can be manipulated by many force powers, they cannot sense the Force. You can not use force powers or take levels in forcecasting classes.",
section: "Species Traits",
type: Boolean
hint: "SW5E.FlagsForceInsensitiveHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"foreignBiology": {
name: "SW5E.FlagsForeignBiology",
hint: "You wear a breathing apparatus because many atmospheres in the galaxy differ from that of your species' homeworld. If your apparatus is removed while you are in such an environment, you lose consciousness.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsForeignBiology",
hint: "SW5E.FlagsForeignBiologyHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"furyOfTheSmall": {
name: "SW5E.FlagsFuryOfTheSmall",
hint: "When you damage a creature with an attack or a power and the creature's size is larger than yours, you can cause the attack or power to deal extra damage to the creature. The extra damage equals your level. Once you use this trait, you can't use it again until you finish a short or long rest.",
section: "Species Traits",
name: "SW5E.FlagsFuryOfTheSmall",
hint: "SW5E.FlagsFuryOfTheSmallHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"grovelCowerAndBeg": {
name: "SW5E.FlagsGrovelCowerAndBeg",
hint: "As an action on your turn, you can cower pathetically to distract nearby foes. Until the end of your next turn, your allies gain advantage on attack rolls against enemies within 10 feet of you that can see you. Once you use this trait, you cant use it again until you finish a short or long rest.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsGrovelCowerAndBeg",
hint: "SW5E.FlagsGrovelCowerAndBegHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"inscrutable": {
name: "SW5E.FlagsInscrutable",
hint: "Your calm demeaner and control make you hard to read. Wisdom (Insight) checks made against you have disadvantage, and you have advantage on any saving throw against an effect that would read your thoughts.",
section: "Species Traits",
name: "SW5E.FlagsInscrutable",
hint: "SW5E.FlagsInscrutableHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"keenSenses": {
name: "SW5E.FlagsKeenSenses",
hint: "You have advantage on Wisdom (Perception) checks that involve using particular senses (see your species' traits for details).",
section: "Species Traits",
hint: "SW5E.FlagsKeenSensesHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"longlimbed": {
name: "SW5E.FlagsLongLimbed",
hint: "When you make a melee attack on your turn, your reach for it is 5 feet greater than normal.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsLongLimbed",
hint: "SW5E.FlagsLongLimbedHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"maintenanceMode": {
name: "SW5E.FlagsMaintenanceMode",
hint: "Rather than sleep, you enter an inactive state to perform routine maintenance for 4 hours each day. You have disadvantage on Wisdom (Perception) checks while performing maintenance.",
section: "Species Traits",
type: Boolean
hint: "SW5E.FlagsMaintenanceModeHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"maskOfTheWild": {
name: "SW5E.FlagsMaskOfTheWild",
hint: "You can attempt to hide even when you are only lightly obscured by foliage, heavy rain, falling snow, mist, and other natural phenomena.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsMaskOfTheWild",
hint: "SW5E.FlagsMaskOfTheWildHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"multipleHearts": {
name: "SW5E.FlagsMultipleHearts",
hint: "When you are reduced to 0 hit points but not killed outright, you can drop to 1 hit point instead. You can't use this feature again until you finish a long rest.",
section: "Species Traits",
name: "SW5E.FlagsMultipleHearts",
hint: "SW5E.FlagsMultipleHeartsHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"naturallyStealthy": {
name: "SW5E.FlagsNaturallyStealthy",
hint: "You can attempt to hide even when you are obscured only by a creature that is your size or larger than you.",
section: "Species Traits",
hint: "SW5E.FlagsNaturallyStealthyHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"nimbleAgility": {
name: "SW5E.FlagsNimbleAgility",
hint: "Your reflexes and agility allow you to move with a burst of speed. When you move on your turn in combat, you can double your speed until the end of the turn. Once you use this trait, you can't use it again until you move 0 feet on one of your turns.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsNimbleAgility",
hint: "SW5E.FlagsNimbleAgilityHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"nimbleEscape": {
name: "SW5E.FlagsNimbleEscape",
hint: "You can take the Disengage or Hide action as a bonus action.",
section: "Species Traits",
hint: "SW5E.FlagsNimbleEscapeHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"nimbleness": {
name: "SW5E.FlagsNimbleness",
hint: "You can move through the space of any creature that is of a size larger than yours.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsNimbleness",
hint: "SW5E.FlagsNimblenessHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"pintsized": {
name: "SW5E.FlagsPintsized",
hint: "Your tiny stature makes it hard for you to wield bigger weapons. You cant use medium or heavy shields. Additionally, you cant wield weapons with the two-handed or versatile property, and you can only wield one-handed weapons in two hands unless they have the light property.",
section: "Species Traits",
type: Boolean
hint: "SW5E.FlagsPintsizedHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"powerfulBuild": {
name: "SW5E.FlagsPowerfulBuild",
hint: "SW5E.FlagsPowerfulBuildHint",
section: "Species Traits",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"precognition": {
name: "SW5E.FlagsPrecognition",
hint: "You can see brief visions of the future that allow you to turn failures into successes. When you roll a 1 on an attack roll, ability check, or saving throw, you can reroll the die and must use the new roll.",
section: "Species Traits",
name: "SW5E.FlagsPrecognition",
hint: "SW5E.FlagsPrecognitionHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"programmer": {
name: "SW5E.FlagsProgrammer",
hint: "Whenever you make an Intelligence (Technology) check related to computers, you are considered to have expertise in the Technology skill.",
section: "Species Traits",
hint: "SW5E.FlagsProgrammerHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"puny": {
name: "SW5E.FlagsPuny",
hint: "Members of your species are too small to pack much of a punch. You have disadvantage on Strength saving throws, and when determining your bonus to attack and damage rolls for weapon attacks using Strength, you cant add more than +3.",
section: "Species Traits",
type: Boolean
hint: "SW5E.FlagsPunyHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"rapidReconstruction": {
name: "SW5E.FlagsRapidReconstruction",
hint: "You are built with internal repair mechanisms. As a bonus action, you can choose to spend one of your Hit Dice to recover hit points.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsRapidReconstruction",
hint: "SW5E.FlagsRapidReconstructionHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"rapidlyRegenerative": {
name: "SW5E.FlagsRapidlyRegenerative",
hint: "You heal quickly, both at will and in response to danger. As a bonus action, you can choose to spend one of your Hit Dice to recover hit points. Additionally, when you take damage, you can use your reaction and expend a Hit Die to regain hit points as long as the damage would not reduce your hit points to 0.",
section: "Species Traits",
name: "SW5E.FlagsRapidlyRegenerative",
hint: "SW5E.FlagsRapidlyRegenerativeHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"regenerative": {
name: "SW5E.FlagsRegenerative",
hint: "When you take damage, you can use your reaction and expend a Hit Die to regain hit points as long as the damage would not reduce your hit points to 0.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsRegenerative",
hint: "SW5E.FlagsRegenerativeHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"savageAttacks": {
name: "SW5E.FlagsSavageAttacks",
name: "SW5E.FlagsSavageAttacks",
hint: "SW5E.FlagsSavageAttacksHint",
section: "Species Traits",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"shapechanger": {
name: "SW5E.FlagsShapechanger",
hint: "As an action, you can change your appearance and your voice. You determine the specifics of the changes, including your coloration, hair length, and sex. You can also adjust your height and weight, but not so much that your size changes. You can make yourself appear as a member of another species, though none of your game statistics change. You can't duplicate the appearance of a creature you've never seen, and you must adopt a form that has the same basic arrangement of limbs that you have. Your clothing and equipment aren't changed by this trait. You stay in the new form until you use an action to revert to your true form or until you die.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsShapechanger",
hint: "SW5E.FlagsShapechangerHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"strongLegged": {
name: "SW5E.FlagsStrongLegged",
hint: "When you make a long jump, you can cover a number of feet up to twice your Strength score. When you make a high jump, you can leap a number of feet up into the air equal to 3 + twice your Strength modifier.",
section: "Species Traits",
name: "SW5E.FlagsStrongLegged",
hint: "SW5E.FlagsStrongLeggedHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"sunlightSensitivity": {
name: "SW5E.FlagsSunlightSensitivity",
hint: "You have disadvantage on attack rolls and on Wisdom (Perception) checks that rely on sight when you, the target of your attack, or whatever you are trying to perceive is in direct sunlight.",
section: "Species Traits",
name: "SW5E.FlagsSunlightSensitivity",
hint: "SW5E.FlagsSunlightSensitivityHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"surpriseAttack": {
name: "SW5E.FlagsSurpriseAttack",
hint: "If you surprise a creature and hit it with an attack on your first turn in combat, the attack deals an extra 2d6 damage to it. You can use this trait only once per combat.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsSurpriseAttack",
hint: "SW5E.FlagsSurpriseAttackHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"techImpaired": {
name: "SW5E.FlagsTechImpaired",
hint: "While members of your species can figure out basic technology, they experience difficulty using more complex equipment like wristpads. You cannot use tech powers or take levels in techcasting classes.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsTechImpaired",
hint: "SW5E.FlagsTechImpairedHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"techResistance": {
name: "SW5E.FlagsTechResistance",
hint: "You have advantage on Dexterity and Intelligence saving throws against tech powers.",
section: "Species Traits",
hint: "SW5E.FlagsTechResistanceHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"tinker": {
name: "SW5E.FlagsTinker",
hint: "You have proficiency with tinkers implements. You can use these and spend 1 hour and 100 cr worth of materials to construct a Tiny Device (AC 5, 1 hp). You can take the Use an Object action to have your device cause one of a variety of minor effects (see your species' traits list). You can maintain a number of these devices up to your proficiency bonus at once, and a device stops functioning after 24 hours away from you. You can dismantle the device to reclaim the materials used to create it.",
section: "Species Traits",
name: "SW5E.FlagsTinker",
hint: "SW5E.FlagsTinkerHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"toughness": {
name: "SW5E.FlagsToughness",
hint: "Your hit point maximum increases by 1, and it increases by 1 every time you gain a level.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsToughness",
hint: "SW5E.FlagsToughnessHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"trance": {
name: "SW5E.FlagsTrance",
hint: "Either through meditation or a reduced sleep schedule, you are able to receive the rest you require on a daily basis (see your species' traits for details). After resting in this way, you gain the same benefit that a human does from 8 hours of sleep.",
section: "Species Traits",
type: Boolean
name: "SW5E.FlagsTrance",
hint: "SW5E.FlagsTranceHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"unarmedCombatant": {
name: "SW5E.FlagsUnarmedCombatant",
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: "Species Traits",
hint: "SW5E.FlagsUnarmedCombatantHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"undersized": {
name: "SW5E.FlagsUndersized",
hint: "Your small stature makes it hard for you to wield bigger weapons. You cant 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.",
section: "Species Traits",
hint: "SW5E.FlagsUndersizedHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"unsettlingVisage": {
name: "SW5E.FlagsUnsettlingVisage",
hint: "When a creature you can see makes an attack roll against you, you can use your reaction to impose disadvantage on the roll. You must use this feature before knowing whether the attack hits or misses. Using this trait reveals your shapeshifting nature to any creature within 30 feet that can see you. Once you use this trait, you can't use it again until you finish a short or long rest.",
section: "Species Traits",
type: Boolean
hint: "SW5E.FlagsUnsettlingVisageHint",
section: "SW5E.SpeciesTraits",
type: Boolean
},
"initiativeAdv": {
name: "SW5E.FlagsInitiativeAdv",
hint: "SW5E.FlagsInitiativeAdvHint",
section: "Feats",
section: "SW5E.Features",
type: Boolean
},
"initiativeAlert": {
name: "SW5E.FlagsAlert",
hint: "SW5E.FlagsAlertHint",
section: "Feats",
section: "SW5E.Features",
type: Boolean
},
"jackOfAllTrades": {
name: "SW5E.FlagsJOAT",
hint: "SW5E.FlagsJOATHint",
section: "Feats",
section: "SW5E.Features",
type: Boolean
},
"observantFeat": {
name: "SW5E.FlagsObservant",
hint: "SW5E.FlagsObservantHint",
skills: ['prc','inv'],
section: "Feats",
section: "SW5E.Features",
type: Boolean
},
"reliableTalent": {
name: "SW5E.FlagsReliableTalent",
hint: "SW5E.FlagsReliableTalentHint",
section: "Feats",
section: "SW5E.Features",
type: Boolean
},
"remarkableAthlete": {
name: "SW5E.FlagsRemarkableAthlete",
hint: "SW5E.FlagsRemarkableAthleteHint",
abilities: ['str','dex','con'],
section: "Feats",
section: "SW5E.Features",
type: Boolean
},
"weaponCriticalThreshold": {
name: "SW5E.FlagsWeaponCritThreshold",
hint: "SW5E.FlagsWeaponCritThresholdHint",
section: "Feats",
section: "SW5E.Features",
type: Number,
placeholder: 20
},
"powerCriticalThreshold": {
name: "SW5E.FlagsPowerCritThreshold",
hint: "SW5E.FlagsPowerCritThresholdHint",
section: "Feats",
section: "SW5E.Features",
type: Number,
placeholder: 20
},
"meleeCriticalDamageDice": {
name: "SW5E.FlagsMeleeCriticalDice",
hint: "SW5E.FlagsMeleeCriticalDiceHint",
section: "Feats",
section: "SW5E.Features",
type: Number,
placeholder: 0
}
};
// Configure allowed status flags
SW5E.allowedActorFlags = ["isPolymorphed", "originalActor"].concat(Object.keys(SW5E.characterFlags));
SW5E.allowedActorFlags = ["isPolymorphed", "originalActor", "dataVersion"].concat(Object.keys(SW5E.characterFlags));

6
module/effects.js vendored
View file

@ -37,17 +37,17 @@ export function prepareActiveEffectCategories(effects) {
const categories = {
temporary: {
type: "temporary",
label: "Temporary Effects",
label: "SW5E.EffectsCategoryTemporary",
effects: []
},
passive: {
type: "passive",
label: "Passive Effects",
label: "SW5E.EffectsCategoryPassive",
effects: []
},
inactive: {
type: "inactive",
label: "Inactive Effects",
label: "SW5E.EffectsCategoryInactive",
effects: []
}
};

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

@ -6,9 +6,10 @@ 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});
// Migrate World Actors
for ( let a of game.actors.entities ) {
for await ( let a of game.actors.entities ) {
try {
const updateData = migrateActorData(a.data);
console.log(`Checking Actor entity ${a.name} for migration needs`);
const updateData = await migrateActorData(a.data);
if ( !isObjectEmpty(updateData) ) {
console.log(`Migrating Actor entity ${a.name}`);
await a.update(updateData, {enforceTypes: false});
@ -36,7 +37,7 @@ export const migrateWorld = async function() {
// Migrate Actor Override Tokens
for ( let s of game.scenes.entities ) {
try {
const updateData = migrateSceneData(s.data);
const updateData = await migrateSceneData(s.data);
if ( !isObjectEmpty(updateData) ) {
console.log(`Migrating Scene entity ${s.name}`);
await s.update(updateData, {enforceTypes: false});
@ -79,18 +80,18 @@ export const migrateCompendium = async function(pack) {
const content = await pack.getContent();
// Iterate over compendium entries - applying fine-tuned migration functions
for ( let ent of content ) {
for await ( let ent of content ) {
let updateData = {};
try {
switch (entity) {
case "Actor":
updateData = migrateActorData(ent.data);
updateData = await migrateActorData(ent.data);
break;
case "Item":
updateData = migrateItemData(ent.data);
break;
case "Scene":
updateData = migrateSceneData(ent.data);
updateData = await migrateSceneData(ent.data);
break;
}
if ( isObjectEmpty(updateData) ) continue;
@ -123,7 +124,7 @@ export const migrateCompendium = async function(pack) {
* @param {object} actor The actor data object to update
* @return {Object} The updateData to apply
*/
export const migrateActorData = function(actor) {
export const migrateActorData = async function(actor) {
const updateData = {};
// Actor Data Updates
@ -131,27 +132,40 @@ 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 = await actor.items.reduce(async (memo, i) => {
const results = await memo;
// Migrate the Owned Item
let itemUpdate = migrateItemData(i);
// Migrate the Owned Item
let itemUpdate = await migrateActorItemData(i, actor);
// 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;
console.log(`Migrating Actor ${actor.name}'s ${i.name}`);
return [...results, mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false})];
} else return [...results, 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,22 +205,38 @@ function cleanActorData(actorData) {
*/
export const migrateItemData = function(item) {
const updateData = {};
_migrateItemClassPowerCasting(item, updateData);
_migrateItemAttunement(item, updateData);
return updateData;
};
/* -------------------------------------------- */
/**
* Migrate a single owned actor Item entity to incorporate latest data model changes
* @param item
* @param actor
*/
export const migrateActorItemData = async function(item, actor) {
const updateData = {};
_migrateItemClassPowerCasting(item, updateData);
_migrateItemAttunement(item, updateData);
await _migrateItemPower(item, actor, updateData);
return updateData;
};
/* -------------------------------------------- */
/**
* Migrate a single Scene entity to incorporate changes to the data model of it's actor data overrides
* Return an Object of updateData to be applied
* @param {Object} scene The Scene data to Update
* @return {Object} The updateData to apply
*/
export const migrateSceneData = function(scene) {
export const migrateSceneData = async function(scene) {
const tokens = duplicate(scene.tokens);
return {
tokens: tokens.map(t => {
tokens: await Promise.all(tokens.map(async (t) => {
if (!t.actorId || t.actorLink || !t.actorData.data) {
t.actorData = {};
return t;
@ -216,11 +246,11 @@ export const migrateSceneData = function(scene) {
t.actorId = null;
t.actorData = {};
} else if ( !t.actorLink ) {
const updateData = migrateActorData(token.data.actorData);
const updateData = await migrateActorData(token.data.actorData);
t.actorData = mergeObject(token.data.actorData, updateData);
}
return t;
})
}))
};
};
@ -228,6 +258,72 @@ 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, if not there is no compendium monster so exit
const hasSource = actor?.flags?.core?.sourceId !== undefined;
if (!hasSource) return actor;
// shortcut out if dataVersion flag is set to 1.2.4 or higher
const hasDataVersion = actor?.flags?.sw5e?.dataVersion !== undefined;
if (hasDataVersion && (actor.flags.sw5e.dataVersion === "1.2.4" || isNewerVersion("1.2.4", actor.flags.sw5e.dataVersion))) return actor;
// Check to see what the source of NPC is
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);
}
}
}
// get actor to create new powers
const liveActor = game.actors.get(actor._id);
// create the powers on the actor
liveActor.createEmbeddedEntity("OwnedItem", 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 +351,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
@ -292,6 +449,81 @@ function _migrateActorSenses(actor, 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;
}
/* -------------------------------------------- */
/**
* Update an Power Item's data based on compendium
* @param {Object} item The data object for an item
* @param {Object} actor The data object for the actor owning the item
* @private
*/
async function _migrateItemPower(item, actor, updateData) {
// if item is not a power shortcut out
if (item.type !== "power") return updateData;
console.log(`Checking Actor ${actor.name}'s ${item.name} for migration needs`);
// check for flag.core, if not there is no compendium power so exit
const hasSource = item?.flags?.core?.sourceId !== undefined;
if (!hasSource) return updateData;
// shortcut out if dataVersion flag is set to 1.2.4 or higher
const hasDataVersion = item?.flags?.sw5e?.dataVersion !== undefined;
if (hasDataVersion && (item.flags.sw5e.dataVersion === "1.2.4" || isNewerVersion("1.2.4", item.flags.sw5e.dataVersion))) return updateData;
// Check to see what the source of Power is
const sourceId = item.flags.core.sourceId;
const coreSource = sourceId.substr(0, sourceId.length - 17);
const core_id = sourceId.substr(sourceId.length - 16, 16);
//if power type is not force or tech exit out
let powerType = "none";
if (coreSource === "Compendium.sw5e.forcepowers") powerType = "sw5e.forcepowers";
if (coreSource === "Compendium.sw5e.techpowers") powerType = "sw5e.techpowers";
if (powerType === "none") return updateData;
const corePower = duplicate(await game.packs.get(powerType).getEntity(core_id));
console.log(`Updating Actor ${actor.name}'s ${item.name} from compendium`);
const corePowerData = corePower.data;
// copy Core Power Data over original Power
updateData["data"] = corePowerData;
updateData["flags"] = {"sw5e": {"dataVersion": "1.2.4"}};
return updateData;
//game.packs.get(powerType).getEntity(core_id).then(corePower => {
//})
}
/* -------------------------------------------- */
/**
* Delete the old data.attuned boolean
* @private
@ -303,7 +535,6 @@ function _migrateItemAttunement(item, updateData) {
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: 10 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: 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

View file

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Before After
Before After

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