From f2ca4041d37ba7274dc8cd5a536ab9ce3ebae229 Mon Sep 17 00:00:00 2001 From: Nikolaj Andresen Date: Sun, 7 Jan 2024 14:31:44 +0100 Subject: [PATCH] Add function to preview candidate checks and their associated modifiers for actions (#12682) --- src/module/actor/actions/single-check.ts | 57 ++++++++++++++++++- .../system/action-macros/basic/escape.ts | 37 +++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/module/actor/actions/single-check.ts b/src/module/actor/actions/single-check.ts index cff2b0c9b3f..891de38c4df 100644 --- a/src/module/actor/actions/single-check.ts +++ b/src/module/actor/actions/single-check.ts @@ -1,5 +1,5 @@ import type { ActorPF2e } from "@actor"; -import { ModifierPF2e, RawModifier } from "@actor/modifiers.ts"; +import { ModifierPF2e, RawModifier, StatisticModifier } from "@actor/modifiers.ts"; import { DCSlug } from "@actor/types.ts"; import { DC_SLUGS } from "@actor/values.ts"; import type { ItemPF2e } from "@item"; @@ -39,6 +39,20 @@ interface SingleCheckActionData extends BaseActionData = {}): ActionCheckPreview[] { + const slugs = this.#statistic || this.#action.statistic; + const candidates = Array.isArray(slugs) ? slugs : [slugs]; + + // TODO: append relevant statistic replacements from the actor + + return candidates + .map((candidate) => + this.toActionCheckPreview({ actor: options.actor, rollOptions: this.rollOptions, slug: candidate }), + ) + .filter((preview): preview is ActionCheckPreview => !!preview); + } + override async use(options: Partial = {}): Promise { const modifiers = this.modifiers.map((raw) => new ModifierPF2e(raw)).concat(options.modifiers ?? []); if (options.multipleAttackPenalty) { @@ -136,6 +163,28 @@ class SingleCheckActionVariant extends BaseActionVariant { ): CheckContext | undefined { return ActionMacroHelpers.defaultCheckContext(opts, data); } + + protected toActionCheckPreview(args: { + actor?: ActorPF2e; + rollOptions: string[]; + slug: string; + }): ActionCheckPreview | null { + if (args.actor) { + const statistic = args.actor.getStatistic(args.slug); + if (statistic) { + const modifier = new StatisticModifier(args.slug, statistic.modifiers, args.rollOptions); + return { label: statistic.label, modifier: modifier.totalModifier, slug: args.slug }; + } + } else { + const labels: Record = { + perception: "PF2E.PerceptionLabel", + ...CONFIG.PF2E.saves, + ...CONFIG.PF2E.skillList, + }; + return { label: game.i18n.localize(labels[args.slug] ?? args.slug), slug: args.slug }; + } + return null; + } } class SingleCheckAction extends BaseAction { @@ -154,10 +203,14 @@ class SingleCheckAction extends BaseAction = {}): ActionCheckPreview[] { + return this.getDefaultVariant(options).preview(options); + } + protected override toActionVariant(data?: SingleCheckActionVariantData): SingleCheckActionVariant { return new SingleCheckActionVariant(this, data); } } export { SingleCheckAction, SingleCheckActionVariant }; -export type { SingleCheckActionUseOptions, SingleCheckActionVariantData }; +export type { ActionCheckPreview, SingleCheckActionUseOptions, SingleCheckActionVariantData }; diff --git a/src/module/system/action-macros/basic/escape.ts b/src/module/system/action-macros/basic/escape.ts index db1967b91e3..7f8f086e0f5 100644 --- a/src/module/system/action-macros/basic/escape.ts +++ b/src/module/system/action-macros/basic/escape.ts @@ -1,5 +1,10 @@ import { CharacterPF2e, NPCPF2e, type ActorPF2e } from "@actor"; -import { SingleCheckAction, SingleCheckActionVariant, SingleCheckActionVariantData } from "@actor/actions/index.ts"; +import { + ActionCheckPreview, + SingleCheckAction, + SingleCheckActionVariant, + SingleCheckActionVariantData, +} from "@actor/actions/index.ts"; import { StrikeData } from "@actor/data/base.ts"; import { StatisticModifier } from "@actor/modifiers.ts"; import type { ItemPF2e } from "@item"; @@ -127,6 +132,36 @@ class EscapeActionVariant extends SingleCheckActionVariant { ): CheckContext | undefined { return escapeCheckContext(opts, data); } + + protected override toActionCheckPreview(options: { + actor?: ActorPF2e; + rollOptions: string[]; + slug: string; + }): ActionCheckPreview | null { + return this.#unarmedCheckPreview(options) ?? super.toActionCheckPreview(options); + } + + #unarmedCheckPreview(args: { actor?: ActorPF2e; rollOptions: string[]; slug: string }): ActionCheckPreview | null { + if (args.slug === "unarmed") { + if (args.actor) { + const options = { actor: args.actor, buildContext: () => ({ rollOptions: args.rollOptions }) }; + const data = { rollOptions: args.rollOptions, slug: args.slug }; + const statistic = unarmedStrikeWithHighestModifier(options, data)?.statistic; + if (statistic) { + return { + label: game.i18n.localize("PF2E.TraitUnarmed"), + modifier: statistic.totalModifier, + slug: args.slug, + }; + } + } + return { + label: game.i18n.localize("PF2E.TraitUnarmed"), + slug: args.slug, + }; + } + return null; + } } class EscapeAction extends SingleCheckAction {