Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow posting action macros from Generate Check Prompt macro #15463

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
13 changes: 12 additions & 1 deletion src/scripts/macros/check-prompt/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,15 @@ async function getActions(): Promise<Record<string, string>> {
}
}

export { getActions, loreSkillsFromActors };
function getMacros(): Record<string, string> {
const actions = game.pf2e.actions.map((a) => [a.slug, a.name]);
return actions ? Object.fromEntries(actions) : {};
}

function getVariants(): Record<string, string> {
const actions = game.pf2e.actions;
const variants = actions.map((a) => a.variants.map((v) => [v.slug, v.name])).flat();
return variants ? Object.fromEntries(variants) : {};
}

export { getActions, getMacros, getVariants, loreSkillsFromActors };
69 changes: 52 additions & 17 deletions src/scripts/macros/check-prompt/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { adjustDC, calculateDC, calculateSimpleDC, DCAdjustment } from "@module/
import { ActionDefaultOptions } from "@system/action-macros/types.ts";
import { htmlQuery, signedInteger, tagify, tupleHasValue } from "@util";
import * as R from "remeda";
import { getActions, loreSkillsFromActors } from "./helpers.ts";
import { getActions, getMacros, getVariants, loreSkillsFromActors } from "./helpers.ts";

interface CheckPromptDialogOptions extends ApplicationOptions {
actors: CharacterPF2e[];
Expand All @@ -30,6 +30,8 @@ interface TagifyValue {
class CheckPromptDialog extends Application<CheckPromptDialogOptions> {
#actions?: Record<string, string>;
#lores?: Record<string, string>;
#macros?: Record<string, string>;
#variants?: Record<string, string>;

static override get defaultOptions(): ApplicationOptions {
return {
Expand All @@ -49,6 +51,8 @@ class CheckPromptDialog extends Application<CheckPromptDialogOptions> {

override async getData(): Promise<CheckPromptDialogData> {
this.#actions = await getActions();
this.#macros = getMacros();
this.#variants = getVariants();
this.#lores = loreSkillsFromActors(this.options.actors ?? game.actors.party?.members ?? []);

return {
Expand Down Expand Up @@ -80,31 +84,34 @@ class CheckPromptDialog extends Application<CheckPromptDialogOptions> {
override activateListeners($html: JQuery<HTMLElement>): void {
const html = $html[0];

// Set up tagify fields

const skillEl = html.querySelector<HTMLInputElement>("input#check-prompt-skills");
const skills = {
const skillsAndLoresEl = html.querySelector<HTMLInputElement>("input#check-prompt-skills");
const skillsAndLores = {
...R.mapValues(CONFIG.PF2E.skills, (s) => s.label),
...(R.isEmpty(this.#lores || {}) ? {} : this.#lores),
perception: "PF2E.PerceptionLabel",
};
tagify(skillEl, { whitelist: skills });
tagify(skillsAndLoresEl, { whitelist: skillsAndLores });

const saveEl = html.querySelector<HTMLInputElement>("input#check-prompt-saves");
tagify(saveEl, { whitelist: CONFIG.PF2E.saves });

const loreEl = html.querySelector<HTMLInputElement>("input#check-prompt-lores");
const loreOptions = R.isEmpty(this.#lores || {}) ? {} : { whitelist: this.#lores };
tagify(loreEl, loreOptions);

const actionEl = html.querySelector<HTMLInputElement>("input#check-prompt-actions");
const actionOptions = R.isEmpty(this.#actions || {})
? {}
: { whitelist: this.#actions, enforceWhitelist: false };
const actionOptions = R.isEmpty(this.#macros || {}) ? {} : { whitelist: this.#macros, maxTags: 1 };
tagify(actionEl, actionOptions);

const variantsEl = html.querySelector<HTMLInputElement>("input#check-prompt-variants");
const variants = R.isEmpty(this.#variants || {}) ? {} : { whitelist: this.#variants, maxTags: 1 };
tagify(variantsEl, variants);

const traitEl = html.querySelector<HTMLInputElement>("input#check-prompt-traits");
tagify(traitEl, { whitelist: CONFIG.PF2E.actionTraits, enforceWhitelist: false });

const actionTraitEl = html.querySelector<HTMLInputElement>("input#check-prompt-action-traits");
const actionTraits = R.isEmpty(this.#actions || {})
? {}
: { whitelist: this.#actions, enforceWhitelist: false };
tagify(actionTraitEl, actionTraits);

// Show or hide Roll Options
html.querySelector("div.form-group a.add-roll-options")?.addEventListener("click", () => {
const sectionEl = html.querySelector("section.check-prompt-content");
Expand All @@ -125,19 +132,29 @@ class CheckPromptDialog extends Application<CheckPromptDialogOptions> {
const html = this.element[0];
const types: string[] = [];
const traits: string[] = [];
const actions: string[] = [];
const variants: string[] = [];
const extras: string[] = [];
const activeSkillSaveTab = htmlQuery(html, "section.check-prompt-content section.tab.active");
if (activeSkillSaveTab?.dataset.tab === "skills") {
// get action tags
actions.push(
...this.#htmlQueryTags(html, "input#check-prompt-actions").map((a) =>
a.toLowerCase().replace("action:", "").trim(),
),
);
variants.push(
...this.#htmlQueryTags(html, "input#check-prompt-variants").map((v) => v.toLowerCase().trim()),
);
// get skill tags
types.push(...this.#htmlQueryTags(html, "input#check-prompt-skills"));
// get lore tags
types.push(...this.#htmlQueryTags(html, "input#check-prompt-lores").map((t) => this.#formatLoreType(t)));

// get trait tags
traits.push(...this.#htmlQueryTags(html, "input#check-prompt-traits"));
// get action tags
traits.push(
...this.#htmlQueryTags(html, "input#check-prompt-actions").map((a) => this.#formatActionType(a)),
...this.#htmlQueryTags(html, "input#check-prompt-action-traits").map((a) => this.#formatActionType(a)),
);

if (!!html.querySelector("input#check-prompt-secret:checked") && !traits.includes("secret")) {
Expand All @@ -148,12 +165,19 @@ class CheckPromptDialog extends Application<CheckPromptDialogOptions> {
if (htmlQuery(html, "input#check-prompt-basic-save:checked")) extras.push("basic:true");
}

if (types.length > 0) {
if (types.length > 0 || actions.length > 0) {
const titleEl = htmlQuery<HTMLInputElement>(html, "input#check-prompt-title");
const flavor = titleEl?.value ? `<h4 class="action"><strong>${titleEl.value}</strong></h4><hr>` : "";

const dc = this.#getDC(html);
const content = types.map((type) => this.#constructCheck(type, dc, traits, extras)).join("");
let content: string = "";
if (actions.length > 0 && types.length > 0) {
content = types.map((type) => this.#constructAction(actions[0], variants[0], dc, type, traits)).join("");
} else if (actions.length > 0 && types.length === 0) {
content = actions.map((action) => this.#constructAction(action, variants[0], dc, null, traits)).join("");
} else {
content = types.map((type) => this.#constructCheck(type, dc, traits, extras)).join("");
}

ChatMessagePF2e.create({ author: game.user.id, flavor, content });
}
Expand Down Expand Up @@ -212,6 +236,17 @@ class CheckPromptDialog extends Application<CheckPromptDialogOptions> {
.filter((p) => p);
return `<p>@Check[${parts.join("|")}]</p>`;
}

#constructAction(action: string, variant: string | null, dc: number | null, statistic: string | null, traits: string[]): string {
const parts = [
action,
variant ? `variant=${variant}` : null,
statistic ? `statistic=${statistic}` : null,
Number.isInteger(dc) ? `dc=${dc}` : null,
traits.length ? `traits=${traits.join(",")}` : null,
];
return `[[/act ${parts.join(" ")}]]`;
}
}

export async function checkPrompt(options: ActionDefaultOptions = {}): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions static/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
}
},
"ActionActionTypeLabel": "Action Type",
"ActionActionVariants": "Action Variants",
"ActionActionsLabel": "Actions",
"ActionBrowserSearchHint": "You can search for name or custom attributes. Possible searchable attributes are:<br> source, spellType, level, materials, target, range, time, duration, damage, damageType, save, concentration, ritual, ability and classes. <br>Example: 'fire, damage:d6' to show all spells that have fire in their name and a d6 in the damage",
"ActionDeathNoteLabel": "Death Note",
Expand Down
11 changes: 7 additions & 4 deletions static/templates/gm/check-prompt.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,13 @@
<section class="check-prompt-content">
<section class="tab skill-prompt active" data-tab="skills">
<div class="form-group">
<input id="check-prompt-skills" placeholder="{{localize "PF2E.Actor.Party.CheckPrompt.ChooseSkills"}}" />
<input id="check-prompt-actions" placeholder="{{localize "PF2E.ActionActionsLabel"}}" />
</div>
<div class="form-group lores">
<input id="check-prompt-lores" placeholder="{{localize "PF2E.Actor.Party.CheckPrompt.ChooseLores"}}" />
<div class="form-group">
<input id="check-prompt-variants" placeholder="{{localize "PF2E.ActionActionVariants"}}" />
</div>
<div class="form-group">
<input id="check-prompt-skills" placeholder="{{localize "PF2E.Actor.Party.CheckPrompt.ChooseSkills"}}" />
</div>
<div class="form-group">
<div class="form-group add-roll-options-group">
Expand All @@ -72,7 +75,7 @@
</div>
<div class="roll-options hidden">
<div class="form-group">
<input id="check-prompt-actions" placeholder="{{localize "PF2E.ActionActionsLabel"}}" />
<input id="check-prompt-action-traits" placeholder="{{localize "PF2E.ActionActionsLabel"}}" />
</div>
<div class="form-group">
<input id="check-prompt-traits" placeholder="{{localize "PF2E.ChatRollDetails.RollOptions"}}" />
Expand Down