Skip to content

Commit

Permalink
Add function to preview candidate checks and their associated modifie…
Browse files Browse the repository at this point in the history
…rs for actions (#12682)
  • Loading branch information
nikolaj-a authored Jan 7, 2024
1 parent fa7f024 commit f2ca404
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 3 deletions.
57 changes: 55 additions & 2 deletions src/module/actor/actions/single-check.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -39,6 +39,20 @@ interface SingleCheckActionData extends BaseActionData<SingleCheckActionVariantD
statistic: string | string[];
}

interface ActionVariantCheckPreviewOptions {
actor: ActorPF2e;
}

interface ActionCheckPreviewOptions extends ActionVariantCheckPreviewOptions {
variant: string;
}

interface ActionCheckPreview {
label: string;
modifier?: number;
slug: string;
}

interface SingleCheckActionUseOptions extends ActionUseOptions {
difficultyClass: CheckDC | string;
modifiers: ModifierPF2e[];
Expand Down Expand Up @@ -88,6 +102,19 @@ class SingleCheckActionVariant extends BaseActionVariant {
return this.#statistic ?? this.#action.statistic;
}

preview(options: Partial<ActionVariantCheckPreviewOptions> = {}): 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<SingleCheckActionUseOptions> = {}): Promise<CheckResultCallback[]> {
const modifiers = this.modifiers.map((raw) => new ModifierPF2e(raw)).concat(options.modifiers ?? []);
if (options.multipleAttackPenalty) {
Expand Down Expand Up @@ -136,6 +163,28 @@ class SingleCheckActionVariant extends BaseActionVariant {
): CheckContext<ItemType> | 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<string, string> = {
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<SingleCheckActionVariantData, SingleCheckActionVariant> {
Expand All @@ -154,10 +203,14 @@ class SingleCheckAction extends BaseAction<SingleCheckActionVariantData, SingleC
this.statistic = data.statistic;
}

preview(options: Partial<ActionCheckPreviewOptions> = {}): 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 };
37 changes: 36 additions & 1 deletion src/module/system/action-macros/basic/escape.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -127,6 +132,36 @@ class EscapeActionVariant extends SingleCheckActionVariant {
): CheckContext<ItemType> | 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 {
Expand Down

0 comments on commit f2ca404

Please sign in to comment.