forked from GitHub-Mirrors/foundry-sw5e
Update Core to 1.4.1
Update Core to 1.4.1 and internal Version to 1.4.1.R1-A8
This commit is contained in:
parent
f16383841b
commit
5bb253d9c3
56 changed files with 5440 additions and 3827 deletions
|
@ -27,7 +27,7 @@ export default class Actor5e extends Actor {
|
|||
*/
|
||||
get classes() {
|
||||
if (this._classes !== undefined) return this._classes;
|
||||
if (this.data.type !== "character") return (this._classes = {});
|
||||
if (!["character", "npc"].includes(this.data.type)) return (this._classes = {});
|
||||
return (this._classes = this.items
|
||||
.filter((item) => item.type === "class")
|
||||
.reduce((obj, cls) => {
|
||||
|
@ -52,6 +52,7 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/** @override */
|
||||
prepareData() {
|
||||
this._preparationWarnings = [];
|
||||
super.prepareData();
|
||||
|
||||
// iterate over owned items and recompute attributes that depend on prepared actor data
|
||||
|
@ -62,6 +63,7 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/** @override */
|
||||
prepareBaseData() {
|
||||
this._prepareBaseArmorClass(this.data);
|
||||
switch (this.data.type) {
|
||||
case "character":
|
||||
return this._prepareCharacterData(this.data);
|
||||
|
@ -74,6 +76,16 @@ export default class Actor5e extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
applyActiveEffects() {
|
||||
// The Active Effects do not have access to their parent at preparation time so we wait until this stage to
|
||||
// determine whether they are suppressed or not.
|
||||
this.effects.forEach((e) => e.determineSuppression());
|
||||
return super.applyActiveEffects();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
|
@ -146,6 +158,12 @@ export default class Actor5e extends Actor {
|
|||
|
||||
// Prepare power-casting data
|
||||
this._computeDerivedPowercasting(this.data);
|
||||
|
||||
// Prepare armor class data
|
||||
const ac = this._computeArmorClass(data);
|
||||
this.armor = ac.equippedArmor || null;
|
||||
this.shield = ac.equippedShield || null;
|
||||
if (ac.warnings) this._preparationWarnings.push(...ac.warnings);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -425,6 +443,21 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Initialize derived AC fields for Active Effects to target.
|
||||
* @param actorData
|
||||
* @private
|
||||
*/
|
||||
_prepareBaseArmorClass(actorData) {
|
||||
const ac = actorData.data.attributes.ac;
|
||||
ac.base = 10;
|
||||
ac.shield = ac.bonus = ac.cover = 0;
|
||||
this.armor = null;
|
||||
this.shield = null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare data related to the power-casting capabilities of the Actor
|
||||
* @private
|
||||
|
@ -658,6 +691,103 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Determine a character's AC value from their equipped armor and shield.
|
||||
* @param {object} data Note that this object will be mutated.
|
||||
* @return {{
|
||||
* calc: string,
|
||||
* value: number,
|
||||
* base: number,
|
||||
* shield: number,
|
||||
* bonus: number,
|
||||
* cover: number,
|
||||
* flat: number,
|
||||
* equippedArmor: Item5e,
|
||||
* equippedShield: Item5e,
|
||||
* warnings: string[]
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
_computeArmorClass(data) {
|
||||
// Get AC configuration and apply automatic migrations for older data structures
|
||||
const ac = data.attributes.ac;
|
||||
ac.warnings = [];
|
||||
let cfg = CONFIG.SW5E.armorClasses[ac.calc];
|
||||
if (!cfg) {
|
||||
ac.calc = "flat";
|
||||
if (Number.isNumeric(ac.value)) ac.flat = Number(ac.value);
|
||||
cfg = CONFIG.SW5E.armorClasses.flat;
|
||||
}
|
||||
|
||||
// Identify Equipped Items
|
||||
const armorTypes = new Set(Object.keys(CONFIG.SW5E.armorTypes));
|
||||
const {armors, shields} = this.itemTypes.equipment.reduce(
|
||||
(obj, equip) => {
|
||||
const armor = equip.data.data.armor;
|
||||
if (!equip.data.data.equipped || !armorTypes.has(armor?.type)) return obj;
|
||||
if (armor.type === "shield") obj.shields.push(equip);
|
||||
else obj.armors.push(equip);
|
||||
return obj;
|
||||
},
|
||||
{armors: [], shields: []}
|
||||
);
|
||||
|
||||
// Determine base AC
|
||||
switch (ac.calc) {
|
||||
// Flat AC (no additional bonuses)
|
||||
case "flat":
|
||||
ac.value = ac.flat;
|
||||
return ac;
|
||||
|
||||
// Natural AC (includes bonuses)
|
||||
case "natural":
|
||||
ac.base = ac.flat;
|
||||
break;
|
||||
|
||||
// Equipment-based AC
|
||||
case "default":
|
||||
if (armors.length) {
|
||||
if (armors.length > 1) ac.warnings.push("SW5E.WarnMultipleArmor");
|
||||
const armorData = armors[0].data.data.armor;
|
||||
const isHeavy = armorData.type === "heavy";
|
||||
ac.dex = isHeavy ? 0 : Math.min(armorData.dex ?? Infinity, data.abilities.dex.mod);
|
||||
ac.base = (armorData.value ?? 0) + ac.dex;
|
||||
ac.equippedArmor = armors[0];
|
||||
} else {
|
||||
ac.dex = data.abilities.dex.mod;
|
||||
ac.base = 10 + ac.dex;
|
||||
}
|
||||
break;
|
||||
|
||||
// Formula-based AC
|
||||
default:
|
||||
let formula = ac.calc === "custom" ? ac.formula : cfg.formula;
|
||||
const rollData = this.getRollData();
|
||||
try {
|
||||
const replaced = Roll.replaceFormulaData(formula, rollData);
|
||||
ac.base = Roll.safeEval(replaced);
|
||||
} catch (err) {
|
||||
ac.warnings.push("SW5E.WarnBadACFormula");
|
||||
const replaced = Roll.replaceFormulaData(CONFIG.SW5E.armorClasses.default.formula, rollData);
|
||||
ac.base = Roll.safeEval(replaced);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Equipped Shield
|
||||
if (shields.length) {
|
||||
if (shields.length > 1) ac.warnings.push("SW5E.WarnMultipleShields");
|
||||
ac.shield = shields[0].data.data.armor.value ?? 0;
|
||||
ac.equippedShield = shields[0];
|
||||
}
|
||||
|
||||
// Compute total AC and return
|
||||
ac.value = ac.base + ac.shield + ac.bonus + ac.cover;
|
||||
return ac;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare data related to the power-casting capabilities of the Actor
|
||||
* @private
|
||||
|
@ -712,7 +842,12 @@ export default class Actor5e extends Actor {
|
|||
if (game.settings.get("sw5e", "currencyWeight") && actorData.data.currency) {
|
||||
const currency = actorData.data.currency;
|
||||
const numCoins = Object.values(currency).reduce((val, denom) => (val += Math.max(denom, 0)), 0);
|
||||
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||
|
||||
const currencyPerWeight = game.settings.get("sw5e", "metricWeightUnits")
|
||||
? CONFIG.SW5E.encumbrance.currencyPerWeight.metric
|
||||
: CONFIG.SW5E.encumbrance.currencyPerWeight.imperial;
|
||||
|
||||
weight += numCoins / currencyPerWeight;
|
||||
}
|
||||
|
||||
// Determine the encumbrance size class
|
||||
|
@ -729,9 +864,14 @@ export default class Actor5e extends Actor {
|
|||
|
||||
// Compute Encumbrance percentage
|
||||
weight = weight.toNearest(0.1);
|
||||
const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod;
|
||||
|
||||
const strengthMultiplier = game.settings.get("sw5e", "metricWeightUnits")
|
||||
? CONFIG.SW5E.encumbrance.strMultiplier.metric
|
||||
: CONFIG.SW5E.encumbrance.strMultiplier.imperial;
|
||||
|
||||
const max = (actorData.data.abilities.str.value * strengthMultiplier * mod).toNearest(0.1);
|
||||
const pct = Math.clamped((weight * 100) / max, 0, 100);
|
||||
return {value: weight.toNearest(0.1), max, pct, encumbered: pct > 2 / 3};
|
||||
return {value: weight.toNearest(0.1), max, pct, encumbered: pct > 200 / 3};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -741,7 +881,10 @@ export default class Actor5e extends Actor {
|
|||
/** @inheritdoc */
|
||||
async _preCreate(data, options, user) {
|
||||
await super._preCreate(data, options, user);
|
||||
const sourceId = this.getFlag("core", "sourceId");
|
||||
if (sourceId?.startsWith("Compendium.")) return;
|
||||
|
||||
// Some sensible defaults for convenience
|
||||
// Token size category
|
||||
const s = CONFIG.SW5E.tokenSizes[this.data.data.traits.size || "med"];
|
||||
this.data.token.update({width: s, height: s});
|
||||
|
@ -1044,6 +1187,7 @@ export default class Actor5e extends Actor {
|
|||
// Evaluate a global saving throw bonus
|
||||
const parts = [];
|
||||
const data = {};
|
||||
const speaker = options.speaker || ChatMessage.getSpeaker({actor: this});
|
||||
|
||||
// Include a global actor ability save bonus
|
||||
const bonuses = foundry.utils.getProperty(this.data.data, "bonuses.abilities") || {};
|
||||
|
@ -1060,7 +1204,7 @@ export default class Actor5e extends Actor {
|
|||
halflingLucky: this.getFlag("sw5e", "halflingLucky"),
|
||||
targetValue: 10,
|
||||
messageData: {
|
||||
"speaker": options.speaker || ChatMessage.getSpeaker({actor: this}),
|
||||
"speaker": speaker,
|
||||
"flags.sw5e.roll": {type: "death"}
|
||||
}
|
||||
});
|
||||
|
@ -1192,6 +1336,7 @@ export default class Actor5e extends Actor {
|
|||
* @property {number} dhd Hit dice recovered or spent during the rest.
|
||||
* @property {object} updateData Updates applied to the actor.
|
||||
* @property {Array.<object>} updateItems Updates applied to actor's items.
|
||||
* @property {boolean} longRest Whether the rest type was a long rest.
|
||||
* @property {boolean} newDay Whether a new day occurred during the rest.
|
||||
*/
|
||||
|
||||
|
@ -1316,7 +1461,8 @@ export default class Actor5e extends Actor {
|
|||
...hitDiceUpdates,
|
||||
...this._getRestItemUsesRecovery({recoverLongRestUses: longRest, recoverDailyUses: newDay})
|
||||
],
|
||||
newDay: newDay
|
||||
longRest,
|
||||
newDay
|
||||
};
|
||||
|
||||
// Perform updates
|
||||
|
@ -1326,6 +1472,9 @@ export default class Actor5e extends Actor {
|
|||
// Display a Chat Message summarizing the rest effects
|
||||
if (chat) await this._displayRestResultMessage(result, longRest);
|
||||
|
||||
// Call restCompleted hook so that modules can easily perform actions when actors finish a rest
|
||||
Hooks.callAll("restCompleted", this, result);
|
||||
|
||||
// Return data summarizing the rest effects
|
||||
return result;
|
||||
}
|
||||
|
@ -1364,13 +1513,13 @@ export default class Actor5e extends Actor {
|
|||
// Determine the chat message to display
|
||||
if (longRest) {
|
||||
message = "SW5E.LongRestResult";
|
||||
if (dhp !== 0) message += "HP";
|
||||
if (healthRestored) message += "HP";
|
||||
if (dfp !== 0) message += "FP";
|
||||
if (dtp !== 0) message += "TP";
|
||||
if (dhd !== 0) message += "HD";
|
||||
if (diceRestored) message += "HD";
|
||||
} else {
|
||||
message = "SW5E.ShortRestResultShort";
|
||||
if (dhd !== 0 && dhp !== 0) {
|
||||
if (diceRestored && healthRestored) {
|
||||
if (dtp !== 0) {
|
||||
message = "SW5E.ShortRestResultWithTech";
|
||||
} else {
|
||||
|
@ -1581,19 +1730,19 @@ export default class Actor5e extends Actor {
|
|||
/**
|
||||
* Transform this Actor into another one.
|
||||
*
|
||||
* @param {Actor} target The target Actor.
|
||||
* @param {boolean} [keepPhysical] Keep physical abilities (str, dex, con)
|
||||
* @param {boolean} [keepMental] Keep mental abilities (int, wis, cha)
|
||||
* @param {boolean} [keepSaves] Keep saving throw proficiencies
|
||||
* @param {boolean} [keepSkills] Keep skill proficiencies
|
||||
* @param {boolean} [mergeSaves] Take the maximum of the save proficiencies
|
||||
* @param {boolean} [mergeSkills] Take the maximum of the skill proficiencies
|
||||
* @param {boolean} [keepClass] Keep proficiency bonus
|
||||
* @param {boolean} [keepFeats] Keep features
|
||||
* @param {boolean} [keepPowers] Keep powers
|
||||
* @param {boolean} [keepItems] Keep items
|
||||
* @param {boolean} [keepBio] Keep biography
|
||||
* @param {boolean} [keepVision] Keep vision
|
||||
* @param {Actor5e} target The target Actor.
|
||||
* @param {boolean} [keepPhysical] Keep physical abilities (str, dex, con)
|
||||
* @param {boolean} [keepMental] Keep mental abilities (int, wis, cha)
|
||||
* @param {boolean} [keepSaves] Keep saving throw proficiencies
|
||||
* @param {boolean} [keepSkills] Keep skill proficiencies
|
||||
* @param {boolean} [mergeSaves] Take the maximum of the save proficiencies
|
||||
* @param {boolean} [mergeSkills] Take the maximum of the skill proficiencies
|
||||
* @param {boolean} [keepClass] Keep proficiency bonus
|
||||
* @param {boolean} [keepFeats] Keep features
|
||||
* @param {boolean} [keepPowers] Keep powers
|
||||
* @param {boolean} [keepItems] Keep items
|
||||
* @param {boolean} [keepBio] Keep biography
|
||||
* @param {boolean} [keepVision] Keep vision
|
||||
* @param {boolean} [transformTokens] Transform linked tokens too
|
||||
*/
|
||||
async transformInto(
|
||||
|
@ -1649,16 +1798,16 @@ export default class Actor5e extends Actor {
|
|||
d.data.attributes.exhaustion = o.data.attributes.exhaustion; // Keep your prior exhaustion level
|
||||
d.data.attributes.inspiration = o.data.attributes.inspiration; // Keep inspiration
|
||||
d.data.powers = o.data.powers; // Keep power slots
|
||||
d.data.attributes.ac.flat = target.data.data.attributes.ac.value; // Override AC
|
||||
|
||||
// Token appearance updates
|
||||
d.token = {name: d.name};
|
||||
for (let k of ["width", "height", "scale", "img", "mirrorX", "mirrorY", "tint", "alpha", "lockRotation"]) {
|
||||
d.token[k] = source.token[k];
|
||||
}
|
||||
if (!keepVision) {
|
||||
for (let k of ["dimSight", "brightSight", "dimLight", "brightLight", "vision", "sightAngle"]) {
|
||||
d.token[k] = source.token[k];
|
||||
}
|
||||
const vision = keepVision ? o.token : source.token;
|
||||
for (let k of ["dimSight", "brightSight", "dimLight", "brightLight", "vision", "sightAngle"]) {
|
||||
d.token[k] = vision[k];
|
||||
}
|
||||
if (source.token.randomImg) {
|
||||
const images = await target.getTokenImages();
|
||||
|
@ -1745,9 +1894,9 @@ export default class Actor5e extends Actor {
|
|||
const tokens = this.getActiveTokens(true);
|
||||
const updates = tokens.map((t) => {
|
||||
const newTokenData = foundry.utils.deepClone(d.token);
|
||||
if (!t.data.actorLink) newTokenData.actorData = newActor.data;
|
||||
newTokenData._id = t.data._id;
|
||||
newTokenData.actorId = newActor.id;
|
||||
newTokenData.actorLink = true;
|
||||
return newTokenData;
|
||||
});
|
||||
return canvas.scene?.updateEmbeddedDocuments("Token", updates);
|
||||
|
@ -1771,10 +1920,25 @@ export default class Actor5e extends Actor {
|
|||
const baseActor = game.actors.get(this.token.data.actorId);
|
||||
const prototypeTokenData = await baseActor.getTokenData();
|
||||
const tokenUpdate = {actorData: {}};
|
||||
for (let k of ["width", "height", "scale", "img", "mirrorX", "mirrorY", "tint", "alpha", "lockRotation"]) {
|
||||
for (let k of [
|
||||
"width",
|
||||
"height",
|
||||
"scale",
|
||||
"img",
|
||||
"mirrorX",
|
||||
"mirrorY",
|
||||
"tint",
|
||||
"alpha",
|
||||
"lockRotation",
|
||||
"name"
|
||||
]) {
|
||||
tokenUpdate[k] = prototypeTokenData[k];
|
||||
}
|
||||
return this.token.update(tokenUpdate, {recursive: false});
|
||||
await this.token.update(tokenUpdate, {recursive: false});
|
||||
await this.sheet.close();
|
||||
const actor = this.token.getActor();
|
||||
actor.sheet.render(true);
|
||||
return actor;
|
||||
}
|
||||
|
||||
// Obtain a reference to the original actor
|
||||
|
@ -1854,6 +2018,45 @@ export default class Actor5e extends Actor {
|
|||
return type;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Populate a proficiency object with a `selected` field containing a combination of
|
||||
* localizable group & individual proficiencies from `value` and the contents of `custom`.
|
||||
*
|
||||
* @param {object} data Object containing proficiency data
|
||||
* @param {Array.<string>} data.value Array of standard proficiency keys
|
||||
* @param {string} data.custom Semicolon-separated string of custom proficiencies
|
||||
* @param {string} type "armor", "weapon", or "tool"
|
||||
*/
|
||||
static prepareProficiencies(data, type) {
|
||||
const profs = CONFIG.SW5E[`${type}Proficiencies`];
|
||||
const itemTypes = CONFIG.SW5E[`${type}Ids`];
|
||||
|
||||
let values = [];
|
||||
if (data.value) {
|
||||
values = data.value instanceof Array ? data.value : [data.value];
|
||||
}
|
||||
|
||||
data.selected = {};
|
||||
const pack = game.packs.get(CONFIG.SW5E.sourcePacks.ITEMS);
|
||||
for (const key of values) {
|
||||
if (profs[key]) {
|
||||
data.selected[key] = profs[key];
|
||||
} else if (itemTypes && itemTypes[key]) {
|
||||
const item = pack.index.get(itemTypes[key]);
|
||||
data.selected[key] = item.name;
|
||||
} else if (type === "tool" && CONFIG.SW5E.vehicleTypes[key]) {
|
||||
data.selected[key] = CONFIG.SW5E.vehicleTypes[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom entries
|
||||
if (data.custom) {
|
||||
data.custom.split(";").forEach((c, i) => (data.selected[`custom${i + 1}`] = c.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* DEPRECATED METHODS */
|
||||
/* -------------------------------------------- */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue