Skip to content

Commit

Permalink
[foundryvtt#3039] Add method on Actor5e for creating a conc prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
krbz999 committed Mar 7, 2024
1 parent 2a82293 commit b01186f
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 8 deletions.
46 changes: 44 additions & 2 deletions module/documents/actor/actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LongRestDialog from "../../applications/actor/long-rest.mjs";
import ActiveEffect5e from "../active-effect.mjs";
import PropertyAttribution from "../../applications/property-attribution.mjs";
import Item5e from "../item.mjs";
import { createRollLabel } from "../../enrichers.mjs";

/**
* Extend the base Actor class to implement additional system-specific logic.
Expand Down Expand Up @@ -1180,7 +1181,12 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
let effect;
const { effects } = this.concentration;

if ( !target ) return Promise.all(Array.from(effects).map(effect => this.endConcentration(effect)));
if ( !target ) {
return effects.reduce(async (acc, effect) => {
acc = await acc;
return acc.concat(await this.endConcentration(effect));
}, []);
}

if ( foundry.utils.getType(target) === "string" ) effect = effects.find(e => e.id === target);
else if ( target instanceof ActiveEffect5e ) effect = effects.has(target) ? target : null;
Expand Down Expand Up @@ -1218,6 +1224,42 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {

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

/**
* Create a chat message for this actor with a prompt to challenge concentration.
* @param {object} [options]
* @param {number} [options.dc] The target value of the saving throw.
* @param {string} [options.ability] An ability to use instead of the default.
* @returns {Promise<ChatMessage5e>} A promise that resolves to the created chat message.
*/
async challengeConcentration({ dc=10, ability=null }={}) {
const isConcentrating = this.concentration.effects.size > 0;
if ( !isConcentrating ) return null;

const dataset = {
action: "concentration",
dc: dc
};
if ( ability in CONFIG.DND5E.abilities ) dataset.ability = ability;

const config = {
type: "concentration",
format: "short",
icon: true
};

return ChatMessage.implementation.create({
content: await renderTemplate("systems/dnd5e/templates/chat/request-card.hbs", {
dataset: { ...dataset, type: "concentration" },
buttonLabel: createRollLabel({ ...dataset, ...config }),
hiddenLabel: createRollLabel({ ...dataset, ...config, hideDC: true })
}),
whisper: game.users.filter(user => this.testUserPermission(user, "OWNER")),
speaker: ChatMessage.implementation.getSpeaker({ actor: this })
});
}

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

/**
* Determine whether the provided ability is usable for remarkable athlete.
* @param {string} ability Ability type to check.
Expand Down Expand Up @@ -1755,7 +1797,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
isConcentration: true,
targetValue: 10,
advantage: options.advantage || (conc.mode === modes.ADVANTAGE),
disadvantage: options.disadvantage || (conc.mode === modes.DISADVANTAGE),
disadvantage: options.disadvantage || (conc.mode === modes.DISADVANTAGE)
}, options);
options.parts = parts.concat(options.parts ?? []);

Expand Down
2 changes: 1 addition & 1 deletion module/documents/chat-message.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default class ChatMessage5e extends ChatMessage {
// Otherwise conceal action buttons except for saving throw
const buttons = chatCard.find("button[data-action]:not(.apply-effect)");
buttons.each((i, btn) => {
if ( (btn.dataset.action === "save") || (btn.dataset.action === "rollRequest") ) return;
if ( ["save", "rollRequest", "concentration"].includes(btn.dataset.action) ) return;
btn.style.display = "none";
});
}
Expand Down
16 changes: 11 additions & 5 deletions module/enrichers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -805,11 +805,11 @@ function createPassiveTag(label, dataset) {

/**
* Create a label for a roll message.
* @param {object} config Enrichment configuration data.
* @param {object} config Configuration data.
* @returns {string}
*/
function createRollLabel(config) {
const ability = CONFIG.DND5E.abilities[config.ability]?.label;
export function createRollLabel(config) {
const { label: ability, abbreviation } = CONFIG.DND5E.abilities[config.ability] ?? {};
const skill = CONFIG.DND5E.skills[config.skill]?.label;
const toolUUID = CONFIG.DND5E.enrichmentLookup.tools[config.tool];
const tool = toolUUID ? Trait.getBaseItem(toolUUID, { indexOnly: true })?.name : null;
Expand All @@ -833,8 +833,10 @@ function createRollLabel(config) {
label = game.i18n.format(`EDITOR.DND5E.Inline.Check${longSuffix}`, { check: label });
}
break;
case "concentration":
case "save":
label = ability;
if ( config.type === "save" ) label = ability;
else label = `${game.i18n.localize("DND5E.Concentration")} ${ability ? `(${abbreviation})` : ""}`;
if ( showDC ) label = game.i18n.format("EDITOR.DND5E.Inline.DC", { dc: config.dc, check: label });
label = game.i18n.format(`EDITOR.DND5E.Inline.Save${longSuffix}`, { save: label });
break;
Expand All @@ -851,6 +853,7 @@ function createRollLabel(config) {
case "tool":
label = `<i class="fas fa-hammer"></i>${label}`;
break;
case "concentration":
case "save":
label = `<i class="fas fa-shield-heart"></i>${label}`;
break;
Expand Down Expand Up @@ -934,7 +937,7 @@ async function awardAction(event) {
* @returns {Promise}
*/
async function rollAction(event) {
const target = event.target.closest('.roll-link, [data-action="rollRequest"]');
const target = event.target.closest('.roll-link, [data-action="rollRequest"], [data-action="concentration"]');
if ( !target ) return;
event.stopPropagation();

Expand Down Expand Up @@ -962,6 +965,9 @@ async function rollAction(event) {
switch ( type ) {
case "check":
return await actor.rollAbilityTest(ability, options);
case "concentration":
if ( ability in CONFIG.DND5E.abilities ) options.ability = ability;
return actor.rollConcentration(options);
case "damage":
return await rollDamage(event, speaker);
case "save":
Expand Down

0 comments on commit b01186f

Please sign in to comment.