Skip to content

Commit

Permalink
Merge branch '1.6.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
Fyorl committed May 4, 2022
2 parents 4fcd95b + f72c525 commit e0b9117
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 41 deletions.
6 changes: 6 additions & 0 deletions module/active-effect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
* @extends {ActiveEffect}
*/
export default class ActiveEffect5e extends ActiveEffect {

/** @inheritdoc */
static LOG_V10_COMPATIBILITY_WARNINGS = false;

/* -------------------------------------------- */

/**
* Is this active effect currently suppressed?
* @type {boolean}
Expand Down
31 changes: 27 additions & 4 deletions module/actor/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import Item5e from "../item/entity.js";
*/
export default class Actor5e extends Actor {

/** @inheritdoc */
static LOG_V10_COMPATIBILITY_WARNINGS = false;

/* -------------------------------------------- */

/**
* The data source for Actor5e.classes allowing it to be lazily computed.
* @type {object<string, Item5e>}
Expand Down Expand Up @@ -225,8 +230,10 @@ export default class Actor5e extends Actor {
* @param {Item5e[]} items The items being added to the Actor.
* @param {boolean} [prompt=true] Whether or not to prompt the user.
* @returns {Promise<Item5e[]>}
* @deprecated since dnd5e 1.6, targeted for removal in 1.8
*/
async addEmbeddedItems(items, prompt=true) {
console.warn("Actor5e#addEmbeddedItems has been deprecated and will be removed in 1.8.");
let itemsToAdd = items;
if ( !items.length ) return [];

Expand Down Expand Up @@ -1314,18 +1321,34 @@ export default class Actor5e extends Actor {
* Roll hit points for a specific class as part of a level-up workflow.
* @param {Item5e} item The class item whose hit dice to roll.
* @returns {Promise<Roll>} The completed roll.
* @see {@link dnd5e.preRollClassHitPoints}
*/
async rollClassHitPoints(item) {
if ( item.type !== "class" ) throw new Error("Hit points can only be rolled for a class item.");
const denom = item.data.data.hitDice;
const rollData = { formula: `1${item.data.data.hitDice}`, data: item.getRollData() };
const flavor = game.i18n.format("DND5E.AdvancementHitPointsRollMessage", { class: item.name });
const roll = new Roll(`1${denom}`);
await roll.toMessage({
const messageData = {
title: `${flavor}: ${this.name}`,
flavor,
speaker: ChatMessage.getSpeaker({ actor: this }),
"flags.dnd5e.roll": { type: "hitPoints" }
});
};

/**
* A hook event that fires before hit points are rolled for a character's class.
* @function dnd5e.preRollClassHitPoints
* @memberof hookEvents
* @param {Actor5e} actor Actor for which the hit points are being rolled.
* @param {Item5e} item The class item whose hit dice will be rolled.
* @param {object} rollData
* @param {string} rollData.formula The string formula to parse.
* @param {object} rollData.data The data object against which to parse attributes within the formula.
* @param {object} messageData The data object to use when creating the message.
*/
Hooks.callAll("dnd5e.preRollClassHitPoints", this, item, rollData, messageData);

const roll = new Roll(rollData.formula, rollData.data);
await roll.toMessage(messageData);
return roll;
}

Expand Down
2 changes: 2 additions & 0 deletions module/advancement/advancement-confirmation-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class AdvancementConfirmationDialog extends Dialog {
/**
* A helper function that displays the dialog prompting for an item deletion.
* @param {Item5e} item Item to be deleted.
* @returns {Promise<boolean|null>} Resolves with whether advancements should be unapplied. Rejects with null.
*/
static forDelete(item) {
return this.createDialog(
Expand All @@ -35,6 +36,7 @@ export class AdvancementConfirmationDialog extends Dialog {
/**
* A helper function that displays the dialog prompting for leveling down.
* @param {Item5e} item The class whose level is being changed.
* @returns {Promise<boolean|null>} Resolves with whether advancements should be unapplied. Rejects with null.
*/
static forLevelDown(item) {
return this.createDialog(
Expand Down
31 changes: 27 additions & 4 deletions module/advancement/advancement-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,14 @@ export class AdvancementManager extends Application {
const dataClone = foundry.utils.deepClone(itemData);
dataClone._id = foundry.utils.randomID();
if ( itemData.type === "class" ) {
dataClone.data.levels = 0;
if ( game.release.generation === 10 ) dataClone.system.levels = 0
else dataClone.data.levels = 0;
if ( !manager.clone.data.data.details.originalClass ) {
manager.clone.data.update({"data.details.originalClass": dataClone._id});
if ( game.release.generation === 10 ) {
manager.clone.updateSource({"system.details.originalClass": dataClone._id});
} else {
manager.clone.data.update({"data.details.originalClass": dataClone._id});
}
}
}

Expand Down Expand Up @@ -341,7 +346,8 @@ export class AdvancementManager extends Application {
if ( this.step?.class ) {
let level = this.step.class.level;
if ( this.step.type === "reverse" ) level -= 1;
this.step.class.item.data.update({"data.levels": level});
if ( game.release.generation === 10 ) this.step.class.item.updateSource({"system.levels": level});
else this.step.class.item.data.update({"data.levels": level});
this.clone.prepareData();
}

Expand Down Expand Up @@ -458,7 +464,8 @@ export class AdvancementManager extends Application {
if ( this.step?.class ) {
let level = this.step.class.level;
if ( this.step.type === "reverse" ) level -= 1;
this.step.class.item.data.update({"data.levels": level});
if ( game.release.generation === 10 ) this.step.class.item.updateSource({"system.levels": level});
else this.step.class.item.data.update({"data.levels": level});
}

this.clone.prepareData();
Expand Down Expand Up @@ -560,6 +567,22 @@ export class AdvancementManager extends Application {
return obj;
}, { toCreate: [], toUpdate: [], toDelete: this.actor.items.map(i => i.id) });

/**
* A hook event that fires at the final stage of a character's advancement process, before actor and item updates
* are applied.
* @function dnd5e.preAdvancementManagerComplete
* @memberof hookEvents
* @param {AdvancementManager} advancementManager The advancement manager.
* @param {object} actorUpdates Updates to the actor.
* @param {object[]} toCreate Items that will be created on the actor.
* @param {object[]} toUpdate Items that will be updated on the actor.
* @param {string[]} toDelete IDs of items that will be deleted on the actor.
*/
if ( Hooks.call("dnd5e.preAdvancementManagerComplete", this, updates, toCreate, toUpdate, toDelete) === false ) {
console.log("AdvancementManager completion was prevented by the 'preAdvancementManagerComplete' hook.");
return this.close({ skipConfirmation: true });
}

// Apply changes from clone to original actor
await Promise.all([
this.actor.update(updates, { isAdvancement: true }),
Expand Down
3 changes: 2 additions & 1 deletion module/advancement/advancement.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ export class Advancement {

foundry.utils.mergeObject(this.data, updates);
foundry.utils.mergeObject(advancement[idx], updates);
this.item.data.update({"data.advancement": advancement});
if ( game.release.generation === 10 ) this.item.updateSource({"system.advancement": advancement});
else this.item.data.update({"data.advancement": advancement});

return this;
}
Expand Down
14 changes: 11 additions & 3 deletions module/advancement/hit-points.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class HitPointsAdvancement extends Advancement {

/** @inheritdoc */
static availableForItem(item) {
return !item.data.data.advancement.find(a => a.type === "HitPoints");
return !item.advancement.byType.HitPoints?.length;
}

/* -------------------------------------------- */
Expand All @@ -134,7 +134,11 @@ export class HitPointsAdvancement extends Advancement {
if ( value === undefined ) return;

value += actorData.abilities.con?.mod ?? 0;
this.actor.data.update({
if ( game.release.generation === 10 ) this.actor.updateSource({
"system.attributes.hp.max": actorData.attributes.hp.max + value,
"system.attributes.hp.value": actorData.attributes.hp.value + value
});
else this.actor.data.update({
"data.attributes.hp.max": actorData.attributes.hp.max + value,
"data.attributes.hp.value": actorData.attributes.hp.value + value
});
Expand All @@ -157,7 +161,11 @@ export class HitPointsAdvancement extends Advancement {
if ( value === undefined ) return;

value += actorData.abilities.con?.mod ?? 0;
this.actor.data.update({
if ( game.release.generation === 10 ) this.actor.updateSource({
"system.attributes.hp.max": actorData.attributes.hp.max - value,
"system.attributes.hp.value": actorData.attributes.hp.value - value
});
else this.actor.data.update({
"data.attributes.hp.max": actorData.attributes.hp.max - value,
"data.attributes.hp.value": actorData.attributes.hp.value - value
});
Expand Down
12 changes: 7 additions & 5 deletions module/advancement/item-grant.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,15 @@ export class ItemGrantFlow extends AdvancementFlow {
?? this.advancement.data.value.added;
const checked = new Set(Object.values(added ?? {}));

const items = await Promise.all(config.map(fromUuid));
return foundry.utils.mergeObject(super.getData(), {
optional: this.advancement.data.configuration.optional,
items: await Promise.all(config.map(async uuid => {
const item = await fromUuid(uuid);
item.checked = added ? checked.has(uuid) : true;
return item;
}))
items: items.reduce((arr, item) => {
if ( !item ) return arr;
item.checked = added ? checked.has(item.uuid) : true;
arr.push(item);
return arr;
}, [])
});
}

Expand Down
4 changes: 3 additions & 1 deletion module/apps/proficiency-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ export default class ProficiencySelector extends TraitSelector {

// Build the extended index and return a promise for the data
const promise = packObject.getIndex({
fields: ["data.armor.type", "data.toolType", "data.weaponType"]
// TODO: Remove "img" from this index when v10 is required
// see https://gitlab.com/foundrynet/foundryvtt/-/issues/6152
fields: ["data.armor.type", "data.toolType", "data.weaponType", "img"]
}).then(index => {
const store = index.reduce((obj, entry) => {
obj[entry._id] = entry;
Expand Down
2 changes: 2 additions & 0 deletions module/apps/select-items-prompt.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/**
* A Dialog to prompt the user to select from a list of items.
* @type {Dialog}
* @deprecated since dnd5e 1.6, targeted for removal in 1.8
*/
export default class SelectItemsPrompt extends Dialog {
constructor(items, dialogData={}, options={}) {
super(dialogData, options);
this.options.classes = ["dnd5e", "dialog", "select-items-prompt", "sheet"];
console.warn("SelectItemsPrompt has been deprecated and will be removed in 1.8.");

/**
* Store a reference to the Item documents being used
Expand Down
2 changes: 1 addition & 1 deletion module/combat.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ export const _getInitiativeFormula = function() {

// Optionally apply Dexterity tiebreaker
const tiebreaker = game.settings.get("dnd5e", "initiativeDexTiebreaker");
if ( tiebreaker ) parts.push(actor.data.data.abilities.dex?.value ?? 0 / 100);
if ( tiebreaker ) parts.push((actor.data.data.abilities.dex?.value ?? 0) / 100);
return parts.filter(p => p !== null).join(" + ");
};
11 changes: 2 additions & 9 deletions module/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1193,16 +1193,9 @@ DND5E.CR_EXP_LEVELS = [
/**
* Character features automatically granted by classes & subclasses at certain levels.
* @type {object}
* @deprecated since 1.6.0, targeted for removal in 1.8
*/
Object.defineProperty(DND5E, "classFeatures", {
get() {
console.warn(
"The classFeatures object is deprecated. Please use the new Advancement API to configure class features.");
return ClassFeatures;
},
configurable: true,
enumerable: true
});
DND5E.classFeatures = ClassFeatures;

/**
* Special character flags.
Expand Down
19 changes: 13 additions & 6 deletions module/item/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import Proficiency from "../actor/proficiency.js";
*/
export default class Item5e extends Item {

/** @inheritdoc */
static LOG_V10_COMPATIBILITY_WARNINGS = false;

/* -------------------------------------------- */

/**
* Caches an item linked to this one, such as a subclass associated with a class.
* @type {Item5e}
Expand Down Expand Up @@ -236,10 +241,9 @@ export default class Item5e extends Item {
* @type {object}
*/
get scaleValues() {
if ( !["class", "subclass"].includes(this.type) ) return {};
if ( !["class", "subclass"].includes(this.type) || !this.advancement.byType.ScaleValue ) return {};
const level = this.type === "class" ? this.data.data.levels : this.class?.data.data.levels ?? 0;
return Object.values(this.advancement.byId).reduce((obj, advancement) => {
if ( (advancement.data.type !== "ScaleValue") ) return obj;
return this.advancement.byType.ScaleValue.reduce((obj, advancement) => {
obj[advancement.identifier] = advancement.prepareValue(level);
return obj;
}, {});
Expand Down Expand Up @@ -381,13 +385,16 @@ export default class Item5e extends Item {
byLevel: Object.fromEntries(
Array.fromRange(CONFIG.DND5E.maxLevel + 1).slice(minAdvancementLevel).map(l => [l, []])
),
byType: {},
needingConfiguration: []
};
for ( const advancementData of this.data.data.advancement ?? [] ) {
const Advancement = game.dnd5e.advancement.types[`${advancementData.type}Advancement`];
if ( !Advancement ) continue;
const advancement = new Advancement(this, advancementData);
this.advancement.byId[advancement.id] = advancement;
this.advancement.byType[advancementData.type] ??= [];
this.advancement.byType[advancementData.type].push(advancement);
advancement.levels.forEach(l => this.advancement.byLevel[l].push(advancement));
if ( !advancement.levels.length ) this.advancement.needingConfiguration.push(advancement);
}
Expand Down Expand Up @@ -441,6 +448,7 @@ export default class Item5e extends Item {
const itemData = this.data.data;
if ( !this.hasDamage || !itemData || !this.isOwned ) return [];
const rollData = this.getRollData();
const damageLabels = { ...CONFIG.DND5E.damageTypes, ...CONFIG.DND5E.healingTypes };
const derivedDamage = itemData.damage?.parts?.map(damagePart => {
let formula;
try {
Expand All @@ -449,7 +457,7 @@ export default class Item5e extends Item {
}
catch(err) { console.warn(`Unable to simplify formula for ${this.name}: ${err}`); }
const damageType = damagePart[1];
return { formula, damageType, label: `${formula} ${CONFIG.DND5E.damageTypes[damageType]}` };
return { formula, damageType, label: `${formula} ${damageLabels[damageType] ?? ""}` };
});
this.labels.derivedDamage = derivedDamage;
return derivedDamage;
Expand Down Expand Up @@ -659,9 +667,8 @@ export default class Item5e extends Item {
consumeSpellLevel = configuration.level === "pact" ? "pact" : `spell${configuration.level}`;
if ( consumeSpellSlot === false ) consumeSpellLevel = null;
const upcastLevel = configuration.level === "pact" ? ad.spells.pact.level : parseInt(configuration.level);
if (upcastLevel !== id.level) {
if ( !Number.isNaN(upcastLevel) && (upcastLevel !== id.level) ) {
item = this.clone({"data.level": upcastLevel}, {keepId: true});
item.data.update({_id: this.id}); // Retain the original ID (needed until 0.8.2+)
item.prepareFinalAttributes(); // Spell save DC, etc...
}
}
Expand Down
4 changes: 2 additions & 2 deletions module/macros.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
* @returns {Promise<boolean>}
*/
export async function create5eMacro(data, slot) {
if ( !("data" in data) ) return ui.notifications.warn(game.i18n.localize("MACRO.5eUnownedWarn"));

const macroData = { type: "script", scope: "actor" };
switch ( data.type ) {
case "Item":
if ( !("data" in data) ) return ui.notifications.warn(game.i18n.localize("MACRO.5eUnownedWarn"));
foundry.utils.mergeObject(macroData, {
name: data.data.name,
img: data.data.img,
Expand All @@ -18,6 +17,7 @@ export async function create5eMacro(data, slot) {
});
break;
case "ActiveEffect":
if ( !("data" in data) ) return ui.notifications.warn(game.i18n.localize("MACRO.5eUnownedWarn"));
foundry.utils.mergeObject(macroData, {
name: data.data.label,
img: data.data.icon,
Expand Down
4 changes: 2 additions & 2 deletions module/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,12 +556,12 @@ function _migrateActorAC(actorData, updateData) {
}

// Migrate ac.base in custom formulas to ac.armor
if ( ac?.formula.includes("@attributes.ac.base") ) {
if ( (typeof ac?.formula === "string") && ac?.formula.includes("@attributes.ac.base") ) {
updateData["data.attributes.ac.formula"] = ac.formula.replaceAll("@attributes.ac.base", "@attributes.ac.armor");
}

// Protect against string values created by character sheets or importers that don't enforce data types
if ( typeof ac?.flat === "string" && Number.isNumeric(ac.flat) ) {
if ( (typeof ac?.flat === "string") && Number.isNumeric(ac.flat) ) {
updateData["data.attributes.ac.flat"] = parseInt(ac.flat);
}

Expand Down
2 changes: 1 addition & 1 deletion module/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function indexFromUuid(uuid) {
if ( parts[0] === "Compendium" ) {
const [, scope, packName, id] = parts;
const pack = game.packs.get(`${scope}.${packName}`);
index = pack.index.get(id);
index = pack?.index.get(id);
}

// World Documents
Expand Down
4 changes: 2 additions & 2 deletions system.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "dnd5e",
"title": "DnD5e - Fifth Edition System",
"description": "A system for playing the fifth edition of the worlds most popular role-playing game in the Foundry Virtual Tabletop environment.",
"version": "1.6.0",
"version": "1.6.1",
"author": "Atropos",
"scripts": [],
"esmodules": ["dnd5e.js"],
Expand Down Expand Up @@ -117,5 +117,5 @@
"compatibleCoreVersion": "9",
"url": "https://gitlab.com/foundrynet/dnd5e",
"manifest": "https://gitlab.com/api/v4/projects/foundrynet%2Fdnd5e/packages/generic/dnd5e/latest/system.json",
"download": "https://gitlab.com/foundrynet/dnd5e/-/releases/release-1.6.0/downloads/dnd5e-release-1.6.0.zip"
"download": "https://gitlab.com/foundrynet/dnd5e/-/releases/release-1.6.1/downloads/dnd5e-release-1.6.1.zip"
}
Loading

0 comments on commit e0b9117

Please sign in to comment.