Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into publish-wiki
Browse files Browse the repository at this point in the history
  • Loading branch information
Fyorl committed Nov 12, 2024
2 parents 35e4dbc + bcc1806 commit a867709
Show file tree
Hide file tree
Showing 23 changed files with 112 additions and 51 deletions.
1 change: 1 addition & 0 deletions dnd5e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ Hooks.once("i18nInit", () => {
racePl: game.i18n.localize("TYPES.Item.raceLegacyPl")
},
DND5E: {
FlagsAlertHint: game.i18n.localize("DND5E.FlagsAlertHintLegacy"),
LanguagesExotic: game.i18n.localize("DND5E.LanguagesExoticLegacy"),
LongRestHint: game.i18n.localize("DND5E.LongRestHintLegacy"),
LongRestHintGroup: game.i18n.localize("DND5E.LongRestHintGroupLegacy"),
Expand Down
3 changes: 2 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2010,7 +2010,8 @@
"DND5E.FlagsInitiativeAdv": "Advantage on Initiative",
"DND5E.FlagsInitiativeAdvHint": "Provided by feats or magical items.",
"DND5E.FlagsAlert": "Alert Feat",
"DND5E.FlagsAlertHint": "Provides +5 to Initiative.",
"DND5E.FlagsAlertHint": "Proficient in Initiative rolls.",
"DND5E.FlagsAlertHintLegacy": "Provides +5 to Initiative.",
"DND5E.FlagsJOAT": "Jack of All Trades",
"DND5E.FlagsJOATHint": "Half-Proficiency to Ability Checks in which you are not already Proficient.",
"DND5E.FlagsObservant": "Observant Feat",
Expand Down
1 change: 1 addition & 0 deletions less/v2/npc.less
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@
position: relative;
width: 52px;
height: 52px;
min-width: unset;
line-height: 48px;
font-size: var(--font-size-18);
z-index: 1;
Expand Down
1 change: 1 addition & 0 deletions module/applications/actor/_module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {default as ActorSheetMixin} from "./sheet-mixin.mjs";
export {default as ActorSheetV2Mixin} from "./sheet-v2-mixin.mjs";

export {default as AbilityConfig} from "./config/ability-config.mjs";
export {default as ArmorClassConfig} from "./config/armor-class-config.mjs";
export {default as BaseProficiencyConfig} from "./config/base-proficiency-config.mjs";
export {default as ConcentrationConfig} from "./config/concentration-config.mjs";
export {default as DamagesConfig} from "./config/damages-config.mjs";
Expand Down
8 changes: 6 additions & 2 deletions module/applications/actor/character-sheet-2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,12 @@ export default class ActorSheet5eCharacter2 extends ActorSheetV2Mixin(ActorSheet
*/
async _onAddFacility(event) {
const { type } = event.target.closest("[data-type]").dataset;
const otherType = type === "basic" ? "special" : "basic";
const result = await CompendiumBrowser.selectOne({
filters: { locked: { types: new Set(["facility"]), additional: { type: { [type]: 1 } } } }
filters: { locked: {
types: new Set(["facility"]),
additional: { type: { [type]: 1, [otherType]: -1 }, level: { max: this.actor.system.details.level } }
} }
});
if ( result ) this._onDropItemCreate(await fromUuid(result));
}
Expand Down Expand Up @@ -639,7 +643,7 @@ export default class ActorSheet5eCharacter2 extends ActorSheetV2Mixin(ActorSheet
async _onDropActivity(event, data) {
if ( !event.target.closest(".favorites") ) return;
const activity = await fromUuid(data.uuid);
if ( !activity ) return;
if ( !activity || (activity.actor !== this.actor) ) return;
const uuid = `${activity.item.getRelativeUUID(this.actor)}.Activity.${activity.id}`;
return this._onDropFavorite(event, { type: "activity", id: uuid });
}
Expand Down
2 changes: 1 addition & 1 deletion module/applications/actor/config/armor-class-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import BaseConfigSheet from "../api/base-config-sheet.mjs";
/**
* Configuration application for armor class calculation.
*/
export default class ActorArmorConfig extends BaseConfigSheet {
export default class ArmorClassConfig extends BaseConfigSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["armor-class"],
Expand Down
11 changes: 6 additions & 5 deletions module/applications/actor/npc-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
// Individual item preparation
this._prepareItem(item, ctx);
if ( item.type === "class" ) ctx.availableLevels = Array.fromRange(CONFIG.DND5E.maxLevel, 1).map(level => {
const delta = level - cls.system.levels;
let label = game.i18n.format("DND5E.LevelNumber", { level });
const delta = level - item.system.levels;
let label = `${level}`;
if ( delta ) label = `${label} (${formatNumber(delta, { signDisplay: "always" })})`;
return { value: delta, label, disabled: delta > maxLevelDelta };
});
Expand Down Expand Up @@ -186,13 +186,14 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
async _updateObject(event, formData) {

// Format NPC Challenge Rating
const crs = {"1/8": 0.125, "⅛": 0.125, "1/4": 0.25, "¼": 0.25, "1/2": 0.5, "½": 0.5};
const crs = { "1/8": 0.125, "⅛": 0.125, "1/4": 0.25, "¼": 0.25, "1/2": 0.5, "½": 0.5 };
let crv = "system.details.cr";
let cr = formData[crv];
if ( cr === "" ) formData[crv] = null;
if ( (cr === "") || (cr === "—") ) formData[crv] = null;
else {
cr = crs[cr] || parseFloat(cr);
if ( !Number.isNaN(cr) ) formData[crv] = cr < 1 ? cr : parseInt(cr);
if ( Number.isNaN(cr) ) cr = null;
else formData[crv] = cr < 1 ? cr : parseInt(cr);
}

// Parent ActorSheet update steps
Expand Down
2 changes: 1 addition & 1 deletion module/applications/actor/sheet-mixin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default Base => class extends Base {
_onDropStackConsumables(itemData, { container=null }={}) {
const droppedSourceId = itemData._stats?.compendiumSource ?? itemData.flags.core?.sourceId;
if ( itemData.type !== "consumable" || !droppedSourceId ) return null;
const similarItem = this.actor.sourcedItems.get("Compendium.dnd5e.items.Item.ytlsBjYsZ7OBSEBs", { legacy: false })
const similarItem = this.actor.sourcedItems.get(droppedSourceId, { legacy: false })
?.filter(i => (i.system.container === container) && (i.name === itemData.name))?.first();
if ( !similarItem ) return null;
return similarItem.update({
Expand Down
8 changes: 4 additions & 4 deletions module/applications/actor/short-rest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export default class ShortRestDialog extends Dialog {

const dice = Object.entries(context.availableHD);
context.denomination = (context.availableHD[this._denom] > 0) ? this._denom : dice.find(([k, v]) => v > 0)?.[0];
}

context.hdOptions = Object.entries(context.availableHD).map(([value, number]) => ({
value, label: `${value} (${number} ${game.i18n.localize("DND5E.available")})`
}));
context.hdOptions = Object.entries(context.availableHD).map(([value, number]) => ({
value, label: `${value} (${number} ${game.i18n.localize("DND5E.available")})`
}));
}

// Determine rest type
const variant = game.settings.get("dnd5e", "restVariant");
Expand Down
20 changes: 17 additions & 3 deletions module/applications/dice/d20-configuration-dialog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,31 @@ export default class D20RollConfigurationDialog extends RollConfigurationDialog

/** @override */
async _prepareButtonsContext(context, options) {
let defaultButton = this.options.defaultButton;
if ( !defaultButton ) {
let advantage = false;
let disadvantage = false;
for ( const roll of this.config.rolls ) {
if ( !roll.options ) continue;
if ( roll.options.advantageMode === CONFIG.Dice.D20Roll.ADV_MODE.ADVANTAGE ) advantage = true;
else if ( roll.options.advantageMode === CONFIG.Dice.D20Roll.ADV_MODE.DISADVANTAGE ) disadvantage = true;
else if ( roll.options.advantage && !roll.options.disadvantage ) advantage = true;
else if ( !roll.options.advantage && roll.options.disadvantage ) disadvantage = true;
}
if ( advantage && !disadvantage ) defaultButton = "advantage";
else if ( !advantage && disadvantage ) defaultButton = "disadvantage";
}
context.buttons = {
advantage: {
default: this.options.defaultButton === "advantage",
default: defaultButton === "advantage",
label: game.i18n.localize("DND5E.Advantage")
},
normal: {
default: !["advantage", "disadvantage"].includes(this.options.defaultButton),
default: !["advantage", "disadvantage"].includes(defaultButton),
label: game.i18n.localize("DND5E.Normal")
},
disadvantage: {
default: this.options.defaultButton === "disadvantage",
default: defaultButton === "disadvantage",
label: game.i18n.localize("DND5E.Disadvantage")
}
};
Expand Down
1 change: 1 addition & 0 deletions module/applications/dice/roll-configuration-dialog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ export default class RollConfigurationDialog extends Dialog5e {
* @param {FormDataExtended} formData Data from the dialog.
*/
static async #handleFormSubmission(event, form, formData) {
if ( formData.has("rollMode") ) this.message.rollMode = formData.get("rollMode");
this.#rolls = this._finalizeRolls(event.submitter?.dataset?.action);
await this.close({ dnd5e: { submitted: true } });
}
Expand Down
11 changes: 11 additions & 0 deletions module/data/activity/attack-data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ export default class AttackActivityData extends BaseActivityData {

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

/** @inheritDoc */
get activationLabels() {
const labels = super.activationLabels;
if ( (this.item.type === "weapon") && this.item.labels?.range && !this.range.override ) {
labels.range = this.item.labels.range;
}
return labels;
}

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

/**
* Abilities that could potentially be used with this attack. Unless a specific ability is specified then
* whichever ability has the highest modifier will be selected when making an attack.
Expand Down
11 changes: 11 additions & 0 deletions module/data/item/facility.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,18 @@ export default class FacilityData extends ItemDataModel.mixin(ActivitiesTemplate

/** @override */
static get compendiumBrowserFilters() {
const { basic, special } = CONFIG.DND5E.facilities.advancement;
const min = Math.min(...Object.keys(basic), ...Object.keys(special));
return new Map([
["level", {
label: "DND5E.FACILITY.FIELDS.level.label",
type: "range",
config: {
keyPath: "system.level",
min,
max: CONFIG.DND5E.maxLevel
}
}],
["type", {
label: "DND5E.FACILITY.FIELDS.type.value.label",
type: "set",
Expand Down
3 changes: 2 additions & 1 deletion module/data/item/spell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ export default class SpellData extends ItemDataModel.mixin(ActivitiesTemplate, I
const context = await super.getCardData(enrichmentOptions);
context.isSpell = true;
context.subtitle = [this.parent.labels.level, CONFIG.DND5E.spellSchools[this.school]?.label].filterJoin(" &bull; ");
context.properties = [];
const { activation, components, duration, range, target } = this.parent.labels;
context.properties = [components?.vsm, activation, duration, range, target].filter(_ => _);
if ( !this.properties.has("material") ) delete context.materials;
return context;
}
Expand Down
4 changes: 2 additions & 2 deletions module/data/item/templates/activities.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default class ActivitiesTemplate extends SystemDataModel {
* @type {boolean}
*/
get hasLimitedUses() {
return !!this._source.uses?.max;
return !!this.uses.max;
}

/* -------------------------------------------- */
Expand Down Expand Up @@ -169,7 +169,7 @@ export default class ActivitiesTemplate extends SystemDataModel {
// Remove any old ternary operators from uses to prevent errors
if ( source.uses?.max?.includes(" ? ") ) source.uses.max = "";
for ( const activity of Object.values(source.activities ?? {}) ) {
if ( activity.uses?.max?.includes(" ? ") ) activity.uses.max = "";
if ( activity?.uses?.max?.includes(" ? ") ) activity.uses.max = "";
}

if ( Array.isArray(source.uses?.recovery) ) return;
Expand Down
9 changes: 2 additions & 7 deletions module/dice/basic-roll.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,9 @@ export default class BasicRoll extends Roll {

/**
* A hook event that fires after roll configuration is complete, but before the roll is evaluated.
<<<<<<< Updated upstream
* Multiple hooks may be called depending on the rolling method (e.g. `postSkillCheckRollConfiguration`,
* `postAbilityTestRollConfiguration`, and `postRollConfiguration` for skill checks). Exact contents of the
* configuration object will also change based on the roll type, but the same objects will always be present.
=======
* Multiple hooks may be called depending on the rolling method (e.g. `dnd5e.postSkillCheckRollConfiguration`,
* `dnd5e.postAbilityTestRollConfiguration`, and `dnd5e.postRollConfiguration` for skill checks).
>>>>>>> Stashed changes
* `dnd5e.postAbilityTestRollConfiguration`, and `dnd5e.postRollConfiguration` for skill checks). Exact contents of
* the configuration object will also change based on the roll type, but the same objects will always be present.
* @function dnd5e.postRollConfiguration
* @memberof hookEvents
* @param {BasicRoll[]} rolls Rolls that have been constructed but not evaluated.
Expand Down
17 changes: 13 additions & 4 deletions module/dice/d20-roll.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import BasicRoll from "./basic-roll.mjs";
* Options that describe a d20 roll.
*
* @typedef {BasicRollOptions} D20RollOptions
* @property {boolean} [advantage] Is the roll granted advantage?
* @property {boolean} [disadvantage] Is the roll granted disadvantage?
* @property {boolean} [advantage] Does this roll potentially have advantage?
* @property {boolean} [disadvantage] Does this roll potentially have disadvantage?
* @property {D20Roll.ADV_MODE} [advantageMode] Final advantage mode.
* @property {number} [criticalSuccess] The value of the d20 die to be considered a critical success.
* @property {number} [criticalFailure] The value of the d20 die to be considered a critical failure.
* @property {boolean} [elvenAccuracy] Use three dice when rolling with advantage.
Expand Down Expand Up @@ -252,14 +253,21 @@ export default class D20Roll extends BasicRoll {
configureModifiers() {
if ( !this.validD20Roll ) return;

if ( this.options.advantageMode === undefined ) {
const { advantage, disadvantage } = this.options;
if ( advantage && !disadvantage ) this.options.advantageMode = this.constructor.ADV_MODE.ADVANTAGE;
else if ( !advantage && disadvantage ) this.options.advantageMode = this.constructor.ADV_MODE.DISADVANTAGE;
else this.options.advantageMode = this.constructor.ADV_MODE.NORMAL;
}

// Determine minimum, taking reliable talent into account
let minimum = this.options.minimum;
if ( this.options.reliableTalent ) minimum = Math.max(minimum ?? -Infinity, 10);

// Directly modify the d20
this.d20.applyFlag("elvenAccuracy", this.options.elvenAccuracy === true);
this.d20.applyFlag("halflingLucky", this.options.halflingLucky === true);
this.d20.applyAdvantage(this.options.advantageMode ?? this.constructor.ADV_MODE.NORMAL);
this.d20.applyAdvantage(this.options.advantageMode);
this.d20.applyRange({ minimum, maximum: this.options.maximum });

// Assign critical and fumble thresholds
Expand All @@ -282,7 +290,8 @@ export default class D20Roll extends BasicRoll {
#createD20Die() {
if ( this.terms[0] instanceof CONFIG.Dice.D20Die ) return;
if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return;
this.terms[0] = new CONFIG.Dice.D20Die({ ...this.terms[0] });
const { number, faces, ...data } = this.terms[0];
this.terms[0] = new CONFIG.Dice.D20Die({ ...data, number, faces });
}

/* -------------------------------------------- */
Expand Down
36 changes: 23 additions & 13 deletions module/documents/actor/actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -591,16 +591,18 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
init.mod = ability.mod ?? 0;

// Initiative proficiency
const isLegacy = game.settings.get("dnd5e", "rulesVersion") === "legacy";
const prof = this.system.attributes.prof ?? 0;
const joat = flags.jackOfAllTrades && (game.settings.get("dnd5e", "rulesVersion") === "legacy");
const joat = flags.jackOfAllTrades && isLegacy;
const ra = this._isRemarkableAthlete(abilityId);
init.prof = new Proficiency(prof, (joat || ra) ? 0.5 : 0, !ra);
const alert = flags.initiativeAlert && !isLegacy;
init.prof = new Proficiency(prof, alert ? 1 : (joat || ra) ? 0.5 : 0, !ra);

// Total initiative includes all numeric terms
const initBonus = simplifyBonus(init.bonus, bonusData);
const abilityBonus = simplifyBonus(ability.bonuses?.check, bonusData);
init.total = init.mod + initBonus + abilityBonus + globalCheckBonus
+ (flags.initiativeAlert ? 5 : 0)
+ (flags.initiativeAlert && isLegacy ? 5 : 0)
+ (Number.isNumeric(init.prof.term) ? init.prof.flat : 0);
}

Expand Down Expand Up @@ -2030,12 +2032,24 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
* @returns {D20Roll|null} The constructed but unevaluated D20Roll.
*/
getInitiativeRoll(options={}) {

// Use a temporarily cached initiative roll
if ( this._cachedInitiativeRoll ) return this._cachedInitiativeRoll.clone();
const config = this.getInitiativeRollConfig(options);
if ( !config ) return null;
const formula = ["1d20"].concat(config.parts).join(" + ");
return new CONFIG.Dice.D20Roll(formula, config.data, config.options);
}

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

/**
* Get an un-evaluated D20Roll instance used to roll initiative for this Actor.
* @param {Partial<InitiativeRollOptions>} options Configuration information for the roll.
* @returns {D20RollConfiguration|null} Roll configuration.
*/
getInitiativeRollConfig(options={}) {
const init = this.system.attributes?.init;
const flags = this.flags.dnd5e ?? {};

const abilityId = init?.ability || CONFIG.DND5E.defaultAbilities.initiative;
const ability = this.system.abilities?.[abilityId];

Expand All @@ -2050,7 +2064,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
}, rollData);

const remarkableAthlete = flags.remarkableAthlete && (game.settings.get("dnd5e", "rulesVersion") === "modern");
if ( flags.initiativeAdv || remarkableAthlete ) options.advantageMode ??= dnd5e.dice.D20Roll.ADV_MODE.ADVANTAGE;
if ( flags.initiativeAdv || remarkableAthlete ) options.advantage ??= true;

// Add exhaustion reduction
this.addRollExhaustion(parts, data);
Expand All @@ -2077,9 +2091,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
*/
Hooks.callAll("dnd5e.preConfigureInitiative", this, rollConfig);

// Create the d20 roll
const formula = ["1d20"].concat(parts).join(" + ");
return new CONFIG.Dice.D20Roll(formula, data, options);
return rollConfig;
}

/* -------------------------------------------- */
Expand All @@ -2090,16 +2102,14 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
* @returns {Promise<void>} A promise which resolves once initiative has been rolled for the Actor
*/
async rollInitiativeDialog(rollOptions={}) {
// Create and configure the Initiative roll
const roll = this.getInitiativeRoll(rollOptions);

const config = {
evaluate: false,
event: rollOptions.event,
hookNames: ["initiativeDialog", "abilityCheck", "d20Test"],
rolls: [{ parts: [roll.formula.replace(roll.d20.formula, "")], options: { ...roll.options, configured: false } }],
rolls: [this.getInitiativeRollConfig(rollOptions)],
subject: this
};
if ( !config.rolls[0] ) return;
const dialog = { options: { title: game.i18n.localize("DND5E.InitiativeRoll") } };
const message = { rollMode: game.settings.get("core", "rollMode") };
const rolls = await CONFIG.Dice.D20Roll.build(config, dialog, message);
Expand Down
2 changes: 1 addition & 1 deletion module/documents/item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ export default class Item5e extends SystemDocumentMixin(Item) {
this.advancement = {
byId: {},
byLevel: Object.fromEntries(
Array.fromRange(CONFIG.DND5E.maxLevel, minAdvancementLevel).map(l => [l, []])
Array.fromRange(CONFIG.DND5E.maxLevel + 1).slice(minAdvancementLevel).map(l => [l, []])
),
byType: {},
needingConfiguration: []
Expand Down
2 changes: 1 addition & 1 deletion packs/_source/monsters/dragon/adult-green-dragon.json
Original file line number Diff line number Diff line change
Expand Up @@ -2426,7 +2426,7 @@
"save": {
"ability": "dex",
"dc": {
"calculation": "",
"calculation": "str",
"formula": "19"
}
},
Expand Down
Loading

0 comments on commit a867709

Please sign in to comment.