This macro or mod using macro to call its functions is inspired by the Conditions Manager macros for PF2 (available below in this post for code references) to create a one macro central access to all basic actions in the game.
The macro will display a window with three columns of buttons. Each button calls one of the functions in PF2 for the selected token (only one token) or player’s associated token, to make a specific basic action check (the list of all functions is in this document, below). The ordering of the actions is in alphabetic order, vertically (as shown on the conditions macro example above)
Read the associated skill proficiency of the selected token to paint the button with specific background. Background is mid-grey (untrained), blue (trained), purple (expert), orange (master), red (legendary).
#424242
#171f67
#3c005e
#664400
#5e0000
Icon for each button on the left side of the button. I will edit the macro to put an icon for each action once the macro has a placeholder in it.
The name of the action and the roll modifier from the character sheet of the associated skill. I.e. Hide (+8). No need to display the skill that is used for the roll.
Clicking the button forces the roll and doesn’t close the window to close, allowing to make further calls. Calling the macro again closes the window if it is open. So does X in the upper corner.
The macro should be very easy to edit and add your own additional entries sicne PF2 will always be expanded with new basic actions. Instead of coding the buttons in the HTML, use an array of data at the beginning that is then used to populate the macro. The array of data can be easily read and edit by GM to add additional entries in an alphabetic order. The code will read the array, break it into three equal parts and use each to build the button lists.
This data array approach allows GM to create separate versions of this macro, such as “Most Frequently Used Actions” or “Less Frequently Used Actions” or “Class-Specific Actions” with unique entries in the array.
If an item has no icon, use this as a placeholder. I will be editing the macro to add missing icons as I find good ones: systems/pf2e/icons/actions/craft/unknown-item.webp
If an option lists Macro instead of a Skill, this means the button will rather open another macro by name, rather than a PF2 function. This is used for few Basic Actions that are handled in PF2 by macros, such as Take Cover, Raise a Shield, Treat Wounds, Recall Knowledge. The color of these options should be a separate one (compatible green color to the rest of the colors, using color picker)
Action Name | Skill | PF2 System Function that can be called as a macro | Icon |
---|---|---|---|
Avoid Notice | Stealth | game.pf2e.actions.avoidNotice({ event: event }); | systems/pf2e/icons/features/classes/surprice-attack.webp |
Balance | Acrobatics | game.pf2e.actions.balance({ event: event }); | https://i.imgur.com/q9zXci0.png |
Climb | Athletics | game.pf2e.actions.climb({ event: event }); | icons/sundries/misc/ladder.webp |
Coerce | Intimidation | game.pf2e.actions.coerce({ event: event }); | https://i.imgur.com/iIDYVQL.png |
Command an Animal | Nature | game.pf2e.actions.commandAnAnimal({ event: event }); | https://i.imgur.com/5EqnTIS.png |
Craft | Crafting | game.pf2e.actions.craft({ event: event }); | https://i.imgur.com/McuTUGb.png |
Create a Diversion - Gesture | Deception | game.pf2e.actions.createADiversion({ event: event, variant: 'gesture' }); | https://i.imgur.com/Rc9vnMX.png |
Create a Diversion - Trick | Deception | game.pf2e.actions.createADiversion({ event: event, variant: 'trick' }); | https://i.imgur.com/u9Pt7q1.png |
Demoralize | Intimidation | game.pf2e.actions.demoralize({ event: event }); | icons/skills/social/intimidation-impressing.webp |
Disarm | Athletics | game.pf2e.actions.disarm({ event: event }); | icons/skills/melee/sword-damaged-broken-glow-red.webp |
Feint | Deception | game.pf2e.actions.feint({ event: event }); | https://i.imgur.com/EIRuIFp.png |
Force Open | Athletics | game.pf2e.actions.forceOpen({ event: event }); | https://i.imgur.com/BkwG0cZ.png |
Gather Information | Diplomacy | game.pf2e.actions.gatherInformation({ event: event }); | |
Grapple | Athletics | game.pf2e.actions.grapple({ event: event }); | icons/skills/melee/unarmed-punch-fist.webp |
Hide | Stealth | game.pf2e.actions.hide({ event: event }); | icons/magic/nature/stealth-hide-eyes-green.webp |
Impersonate | Deception | game.pf2e.actions.impersonate({ event: event }); | |
Jump - High | Athletics | game.pf2e.actions.highJump({ event: event }); | https://i.imgur.com/Ot4sbDt.png |
Jump - Long | Athletics | game.pf2e.actions.longJump({ event: event }); | https://i.imgur.com/Ot4sbDt.png |
Lie | Deception | game.pf2e.actions.lie({ event: event }); | icons/magic/control/mouth-smile-deception-purple.webp |
Make an Impression | Diplomacy | game.pf2e.actions.makeAnImpression({ event: event }); | icons/environment/people/commoner.webp |
Maneuver in Flight | Acrobatics | game.pf2e.actions.maneuverInFlight({ event: event }); | icons/commodities/biological/wing-bird-white.webp |
Pick a Lock | Thievery | game.pf2e.actions.pickALock({ event: event }); | https://i.imgur.com/cb7ejb2.png |
Raise a Shield | Macro | game.pf2e.actions.raiseAShield({ actors: [token?.actor ?? actor ?? game.user.character].filter((actor) => actor) }) | systems/pf2e/icons/actions/raise-a-shield.webp |
Recall Knowledge | Macro | see Recall Knowledge macro | |
Repair | Crafting | game.pf2e.actions.repair({ event: event }); | https://i.imgur.com/McuTUGb.png |
Request | Diplomacy | game.pf2e.actions.request({ event: event }); | icons/skills/social/thumbsup-approval-like.webp |
Seek | Perception | game.pf2e.actions.seek({ event: event }); | https://i.imgur.com/7mTBOrS.png |
Sense Direction | Survival | game.pf2e.actions.senseDirection({ event: event }); | icons/tools/navigation/compass-brass-blue-red.webp |
Sense Motive | Perception | game.pf2e.actions.senseMotive({ event: event }); | https://i.imgur.com/7mTBOrS.png |
Shove | Athletics | game.pf2e.actions.shove({ event: event }); | |
Sneak | Stealth | game.pf2e.actions.sneak({ event: event }); | |
Squeeze | Acrobatics | game.pf2e.actions.squeeze({ event: event }); | |
Swim | Athletics | game.pf2e.actions.swim({ event: event }); | icons/creatures/fish/fish-shark-swimming.webp |
Take Cover | Macro | see Take Cover macro | https://i.imgur.com/5TgWEZF.png |
Track | Survival | game.pf2e.actions.track({ event: event }); | https://i.imgur.com/RgHVtlC.png |
Treat Disease | Medicine | game.pf2e.actions.treatDisease({ event: event }); | icons/magic/nature/root-vine-caduceus-healing.webp |
Treat Poison | Medicine | game.pf2e.actions.treatPoison({ event: event }); | https://i.imgur.com/czVMote.png |
Treat Wounds | Macro | see Treat Wounds macro | icons/magic/nature/root-vine-caduceus-healing.webp |
Trip | Athletics | game.pf2e.actions.trip({ event: event }); | https://i.imgur.com/Sr2Gu19.png |
Tumble Through | Acrobatics | game.pf2e.actions.tumbleThrough({ event: event }); | https://i.imgur.com/33UF9Ok.png |
Macro Icon: https://i.imgur.com/bnrJDpS.png
// noinspection CssUnresolvedCustomProperty
/* eslint-disable no-undef */
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
/**
* This is the list of actions, wich can be edited
*
* Action Interface:
* @param {string} name Name shown as the button text;
* @param {string} skill Name of the skill, to get the bonus in parenthesis from;
* @param {function|string} action Function or Macro ID (in case of a string) to call;
* The call passes an {actors: [actor]} object as argument;
* @param {string|undefined} icon The icon to be shown in the button. If undefined, will
* return the defaultIcon;
*/
var actionList = [
{
name: 'Aid',
skill: '',
action: 'y1rDU9gTz6GVobwf',
icon: 'systems/pf2e/icons/spells/efficient-apport.webp',
},
{
name: "Avoid Notice",
skill: "Stealth",
action: game.pf2e.actions.avoidNotice,
icon: "systems/pf2e/icons/features/classes/surprice-attack.webp"
},
{
name: "Balance",
skill: "Acrobatics",
action: game.pf2e.actions.balance,
icon: "icons/skills/movement/feet-winged-boots-brown.webp"
},
{
name: "Climb",
skill: "Athletics",
action: game.pf2e.actions.climb,
icon: "icons/sundries/misc/ladder.webp"
},
{
name: "Coerce",
skill: "Intimidation",
action: game.pf2e.actions.coerce,
icon: "icons/skills/melee/unarmed-punch-fist.webp"
},
{
name: "Command an Animal",
skill: "Nature",
action: game.pf2e.actions.commandAnAnimal,
icon: "icons/environment/creatures/horse-white.webp"
},
{
name: "Craft",
skill: "Crafting",
action: game.pf2e.actions.craft,
icon: "icons/skills/trades/smithing-anvil-silver-red.webp"
},
{
name: "Create a Diversion - Gesture",
skill: "Deception",
action: "game.pf2e.actions.createADiversion({ event: event, variant: 'gesture' });",
icon: "icons/skills/social/wave-halt-stop.webp"
},
{
name: "Create a Diversion - Trick",
skill: "Deception",
action: "game.pf2e.actions.createADiversion({ event: event, variant: 'trick' });",
icon: "<https://i.imgur.com/u9Pt7q1.png>"
},
{
name: "Demoralize",
skill: "Intimidation",
action: game.pf2e.actions.demoralize,
icon: "icons/skills/social/intimidation-impressing.webp"
},
{
name: "Disarm",
skill: "Athletics",
action: game.pf2e.actions.disarm,
icon: "icons/skills/melee/sword-damaged-broken-glow-red.webp"
},
{
name: "Feint",
skill: "Deception",
action: game.pf2e.actions.feint,
icon: "icons/skills/melee/maneuver-sword-katana-yellow.webp"
},
{
name: "Follow the Expert",
skill: "",
action: "eHtbauiO3n075GUu",
icon: "systems/pf2e/icons/spells/favorable-review.webp"
},
{
name: "Force Open",
skill: "Athletics",
action: game.pf2e.actions.forceOpen,
icon: "icons/equipment/feet/boots-armored-steel.webp"
},
{
name: "Gather Information",
skill: "Diplomacy",
action: game.pf2e.actions.gatherInformation,
icon: "icons/skills/social/diplomacy-handshake.webp"
},
{
name: "Grapple",
skill: "Athletics",
action: game.pf2e.actions.grapple,
icon: "icons/skills/melee/unarmed-punch-fist.webp"
},
{
name: "Hide",
skill: "Stealth",
action: game.pf2e.actions.hide,
icon: "icons/magic/nature/stealth-hide-eyes-green.webp"
},
{
name: "Impersonate",
skill: "Deception",
action: game.pf2e.actions.impersonate,
icon: "icons/equipment/head/mask-carved-scream-tan.webp"
},
{
name: "Jump - High",
skill: "Athletics",
action: game.pf2e.actions.highJump,
icon: "icons/skills/movement/arrows-up-trio-red.webp"
},
{
name: "Jump - Long",
skill: "Athletics",
action: game.pf2e.actions.longJump,
icon: "icons/skills/movement/figure-running-gray.webp"
},
{
name: "Lie",
skill: "Deception",
action: game.pf2e.actions.lie,
icon: "icons/magic/control/mouth-smile-deception-purple.webp"
},
{
name: "Make an Impression",
skill: "Diplomacy",
action: game.pf2e.actions.makeAnImpression,
icon: "icons/environment/people/commoner.webp"
},
{
name: "Maneuver in Flight",
skill: "Acrobatics",
action: game.pf2e.actions.maneuverInFlight,
icon: "icons/commodities/biological/wing-bird-white.webp"
},
{
name: "Pick a Lock",
skill: "Thievery",
action: game.pf2e.actions.pickALock,
icon: "icons/skills/social/theft-pickpocket-bribery-brown.webp"
},
{
name: "Raise a Shield",
skill: "",
action: game.pf2e.actions.raiseAShield,
icon: "systems/pf2e/icons/actions/raise-a-shield.webp"
},
{
name: "Recall Knowledge",
skill: "",
action: "EBdLzeLZyUju18Af",
icon: "icons/skills/trades/academics-study-reading-book.webp"
},
{
name: "Repair",
skill: "Crafting",
action: game.pf2e.actions.repair,
icon: "icons/tools/smithing/anvil.webp"
},
{
name: "Request",
skill: "Diplomacy",
action: game.pf2e.actions.request,
icon: "icons/skills/social/thumbsup-approval-like.webp"
},
{
name: "Seek",
skill: "Perception",
action: game.pf2e.actions.seek,
icon: "icons/tools/scribal/magnifying-glass.webp"
},
{
name: "Sense Direction",
skill: "Survival",
action: game.pf2e.actions.senseDirection,
icon: "icons/tools/navigation/compass-brass-blue-red.webp"
},
{
name: "Sense Motive",
skill: "Perception",
action: game.pf2e.actions.senseMotive,
icon: "icons/environment/people/commoner.webp"
},
{
name: "Shove",
skill: "Athletics",
action: game.pf2e.actions.shove,
icon: "systems/pf2e/icons/spells/hydraulic-push.webp"
},
{
name: "Sneak",
skill: "Stealth",
action: game.pf2e.actions.sneak,
icon: "<https://assets.forge-vtt.com/bazaar/systems/pf2e/assets/icons/conditions/unnoticed.webp>"
},
{
name: "Squeeze",
skill: "Acrobatics",
action: game.pf2e.actions.squeeze,
icon: "<https://i.imgur.com/sQ6eUA4.png>"
},
{
name: "Swim",
skill: "Athletics",
action: game.pf2e.actions.swim,
icon: "icons/creatures/fish/fish-shark-swimming.webp"
},
{
name: "Take Cover",
skill: "",
action: "PC0y5A6tHrtJhqVL",
icon: "systems/pf2e/icons/equipment/shields/tower-shield.webp"
},
{
name: "Track",
skill: "Survival",
action: game.pf2e.actions.track,
icon: "systems/pf2e/icons/conditions/observed.webp"
},
{
name: "Treat Disease",
skill: "Medicine",
action: game.pf2e.actions.treatDisease,
icon: "icons/magic/nature/root-vine-caduceus-healing.webp"
},
{
name: "Treat Poison",
skill: "Medicine",
action: game.pf2e.actions.treatPoison,
icon: "systems/pf2e/icons/effects/treat-poison.webp"
},
{
name: "Treat Wounds",
skill: "Medicine",
action: "BO2iFo14pFYHEIGa",
icon: "icons/magic/nature/root-vine-caduceus-healing.webp"
},
{
name: "Trip",
skill: "Athletics",
action: game.pf2e.actions.trip,
icon: "icons/skills/wounds/bone-broken-marrow-yellow.webp"
},
{
name: "Tumble Through",
skill: "Acrobatics",
action: game.pf2e.actions.tumbleThrough,
icon: "icons/skills/movement/feet-winged-sandals-tan.webp"
},
];
function getActor() {
return (canvas === null || canvas === void 0 ? void 0 : canvas.tokens.controlled.length)
? canvas === null || canvas === void 0 ? void 0 : canvas.tokens.controlled.map(function (token) { return token.actor; })[0]
: game.user.character;
}
function getBestBonuses(actorSkills, party, actionList) {
var _a, _b, _c, _d;
for (var _i = 0, party_1 = party; _i < party_1.length; _i++) {
var actorId = party_1[_i];
var skills = actorSkills.get(actorId);
for (var _e = 0, actionList_1 = actionList; _e < actionList_1.length; _e++) {
var action = actionList_1[_e];
var skill = skills[(_a = action.skill) === null || _a === void 0 ? void 0 : _a.toLowerCase()];
if (!skill)
continue;
var bonus = (_c = (_b = skill.check) === null || _b === void 0 ? void 0 : _b.mod) !== null && _c !== void 0 ? _c : skill.totalModifier;
if (bonus > ((_d = action.best) !== null && _d !== void 0 ? _d : -1))
action.best = bonus;
}
}
}
function createMapOfSkillsPerActor(party) {
var map = new Map();
for (var _i = 0, party_2 = party; _i < party_2.length; _i++) {
var actorId = party_2[_i];
var actor = game.actors.get(actorId);
map.set(actorId, getSkills(actor));
}
return map;
}
function getSkills(actor) {
var a = __assign({ perception: actor.attributes.perception }, actor.skills);
Array.from(a).map(function (skill) {
var _a;
a.check.mod = (_a = skill.check) === null || _a === void 0 ? void 0 : _a.mod;
a.totalModifier = skill.to;
});
return a;
}
function signedNumber(n) {
return n < 0 ? "" + n : "+" + n;
}
function createButton(action, idx, actor, party, actorSkills) {
var _a, _b, _c, _d, _e, _f;
/**
* Color palette to use, representing the rank of skills
*/
var colorPalette = ["#424242", "#171f67", "#3c005e", "#664400", "#5e0000"];
var tooltip = "You are the best in your party";
/**
* Default Icon to be given in case there is no icon parameter in an Action
*/
var defaultIcon = "systems/pf2e/icons/actions/craft/unknown-item.webp";
var skill = actorSkills[(_a = action.skill) === null || _a === void 0 ? void 0 : _a.toLowerCase()];
var rank = (_b = skill === null || skill === void 0 ? void 0 : skill.rank) !== null && _b !== void 0 ? _b : 0;
var bonus = skill ? (_d = (_c = skill.check) === null || _c === void 0 ? void 0 : _c.mod) !== null && _d !== void 0 ? _d : skill.totalModifier : -1;
var best = party.length && party.includes(actor.id) ? bonus >= ((_e = action.best) !== null && _e !== void 0 ? _e : 0) : false;
return "<button class=\\"action-btn ".concat(best ? "glow" : "", "\\" data-action=\\"").concat(idx, "\\" style=\\"background:").concat(colorPalette[rank], "\\"\\n ").concat(best ? "data-tooltip=\\"".concat(tooltip, "\\"") : "", ">\\n <img src=\\"").concat((_f = action.icon) !== null && _f !== void 0 ? _f : defaultIcon, "\\" height=\\"24\\" alt=\\"").concat(action.name, "\\"/>").concat(action.name, " ").concat(skill ? "(" + signedNumber(bonus) + ")" : "", "</button>");
}
function basicActionsMacro() {
/**
* This macro opens a dialog containing a list of actions to be used by the selected Actor
* If no actor is selected, it selects the user's standard character.
* If there is no user character, it shows up a warning notification.
*/
var _a;
// @ts-ignore
var actionDialog = window.actionDialog;
if (actionDialog === null || actionDialog === void 0 ? void 0 : actionDialog.rendered) {
return actionDialog.close();
}
var actor = getActor();
if (!actor) {
return ui.notifications.warn("No character selected!");
}
var party = ((_a = game === null || game === void 0 ? void 0 : game.actors) === null || _a === void 0 ? void 0 : _a.filter(function (x) { return x.hasPlayerOwner; }).filter(function (x) { return x.isOfType("character"); }).filter(function (x) { return x.alliance === "party"; }).filter(function (actor) {
return !actor.system.traits["value"].toString().includes("minion");
}).filter(function (actor) { return !actor.system.traits["value"].toString().includes("eidolon"); }).map(function (actor) { return actor.id; })) || [];
var actorSkills = createMapOfSkillsPerActor(party);
if (party.includes(actor.id))
getBestBonuses(actorSkills, party, actionList);
var columns = 1 + ~~((actionList.length - 1) / 14);
var width = 264 * columns;
var height = 30 + ~~((32 * actionList.length + 1) / columns);
var content = "\\n<style>\\n .pf2e-bg .window-content {\\n background: url('../systems/pf2e/assets/sheet/background.webp');\\n }\\n\\n .action-list {\\n display: flex;\\n flex-wrap: wrap;\\n flex-direction: column;\\n justify-content: flex-start;\\n align-items: center;\\n margin-bottom: 8px;\\n max-height: ".concat(height, "px;\\n }\\n\\n .action-btn {\\n margin: 1px auto;\\n width: 250px;\\n height: fit-content;\\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 50%);\\n text-shadow: none;\\n border: #000;\\n color: #fff;\\n display: flex;\\n align-items: center;\\n }\\n\\n .action-btn img {\\n margin-right: 5px;\\n }\\n\\n .action-btn:hover {\\n text-shadow: 0 0 2px #fff;\\n }\\n\\n .action-list button.glow {\\n --color-glow1: 35;\\n --color-glow2: 50;\\n animation: glow2 alternate infinite 2s;\\n z-index: 1;\\n}\\n\\n@keyframes glow2 {\\n 0% {\\n color: hsl(var(--color-glow2), 90%, 50%);\\n /*border: 1px solid hsl(var(--color-glow2), 100%, 50%);*/\\n /*background: hsla(var(--color-glow2), 70%, 30%, 50%);*/\\n box-shadow: 0 0 2px 2px hsl(var(--color-glow2), 100%, 50%);\\n }\\n 100% {\\n color: hsl(var(--color-glow2), 90%, 50%);\\n /*border: 1px solid hsl(var(--color-glow2), 100%, 50%);*/\\n /*background: hsla(var(--color-glow2), 70%, 30%, 100%);*/\\n box-shadow: 0 0 2px 2px hsl(var(--color-glow2), 100%, 50%);\\n }\\n}\\n\\n\\n</style>\\n<div class=\\"action-list\\">\\n").concat(actionList.map(function (action, idx) { return createButton(action, idx, actor, party, actorSkills.get(actor.id)); }).join(""), "\\n</div>\\n");
// @ts-ignore
window.actionDialog = new Dialog({
title: "Actions (".concat(actor.name, ")"),
content: content,
buttons: {
close: {
icon: "<i class=\\"fas fa-times\\"></i>",
label: "Cancel"
}
},
"default": "close",
render: function (html) {
var action = function (button) {
var _a;
var action = actionList[button.dataset.action];
var current = action.action;
if (typeof current === "string")
(_a = game.macros.get(current)) === null || _a === void 0 ? void 0 : _a.execute();
else
action.action({ actors: [actor] });
};
if ("querySelectorAll" in html) {
html.querySelectorAll(".action-list button").forEach(function (button) {
return button.addEventListener("click", function () { return action(button); });
});
}
}
}, { jQuery: false, width: width, classes: ["pf2e-bg"] }).render(true);
}
basicActionsMacro();
/**
* This macro opens a dialog containing a list of actions to be used by the selected Actor
* If no actor is selected, it selects the user's standard character.
* If there is no user character, it shows up a warning notification.
*/
// List of actors, to evaluate the best skills in the party
const party = ["4bvLhJPGiHGq0F5I","RKgz8QKUIv6v47qA","RPYPiRPaCPCSluGs","qYr5UnQkPA3bOz24"];
if (window.actionDialog?.rendered) {
//@ts-ignore
return window.actionDialog.close();
}
const getActor = () => {
return canvas.tokens.controlled.length ? canvas.tokens.controlled.map((token) => token.actor)[0] : game.user.character;
};
const actor = getActor();
if (!actor) {
//@ts-ignore
return ui.notifications.warn('No character selected!');
}
/**
* Color pallete to use, representing the rank of skills
*/
const colorPallete = ['#424242', '#171f67', '#3c005e', '#664400', '#5e0000'];
/**
* Default Icon to be given in case there is no icon parameter in an Action
*/
const defaultIcon = 'systems/pf2e/icons/actions/craft/unknown-item.webp';
/**
* This is the list of actions, wich can de edited
*
* Action Interface:
* @param {string} name Name shown as the button text;
* @param {string} skill Name of the skill, to get the bonus in parenthesis from;
* @param {function|string} action Function or Macro ID (in case of a string) to call;
* The call passes an {actors: [actor]} object as argument;
* @param {string|undefined} icon The icon to be shown in the button. If undefined, will
* return the defaultIcon;
*/
const actionList = [
{
name: 'Aid',
skill: '',
action: 'y1rDU9gTz6GVobwf',
icon: 'systems/pf2e/icons/spells/efficient-apport.webp',
},
{
name: 'Avoid Notice',
skill: 'Stealth',
action: game.pf2e.actions.avoidNotice,
icon: 'systems/pf2e/icons/features/classes/surprice-attack.webp',
},
{
name: 'Balance',
skill: 'Acrobatics',
action: game.pf2e.actions.balance,
icon: '<https://i.imgur.com/q9zXci0.png>',
},
{
name: 'Climb',
skill: 'Athletics',
action: game.pf2e.actions.climb,
icon: 'icons/sundries/misc/ladder.webp',
},
{
name: 'Coerce',
skill: 'Intimidation',
action: game.pf2e.actions.coerce,
icon: '<https://i.imgur.com/iIDYVQL.png>',
},
{
name: 'Command an Animal',
skill: 'Nature',
action: game.pf2e.actions.commandAnAnimal,
icon: '<https://i.imgur.com/5EqnTIS.png>',
},
{
name: 'Craft',
skill: 'Crafting',
action: game.pf2e.actions.craft,
icon: '<https://i.imgur.com/McuTUGb.png>',
},
{
name: 'Create a Diversion - Gesture',
skill: 'Deception',
action: "game.pf2e.actions.createADiversion({ event: event, variant: 'gesture' });",
icon: '<https://i.imgur.com/Rc9vnMX.png>',
},
{
name: 'Create a Diversion - Trick',
skill: 'Deception',
action: "game.pf2e.actions.createADiversion({ event: event, variant: 'trick' });",
icon: '<https://i.imgur.com/u9Pt7q1.png>',
},
{
name: 'Demoralize',
skill: 'Intimidation',
action: game.pf2e.actions.demoralize,
icon: 'icons/skills/social/intimidation-impressing.webp',
},
{
name: 'Disarm',
skill: 'Athletics',
action: game.pf2e.actions.disarm,
icon: 'icons/skills/melee/sword-damaged-broken-glow-red.webp',
},
{
name: 'Feint',
skill: 'Deception',
action: game.pf2e.actions.feint,
icon: '<https://i.imgur.com/EIRuIFp.png>',
},
{
name: 'Follow an Expert',
skill: '',
action: 'eHtbauiO3n075GUu',
icon: 'systems/pf2e/icons/spells/favorable-review.webp'
},
{
name: 'Force Open',
skill: 'Athletics',
action: game.pf2e.actions.forceOpen,
icon: '<https://i.imgur.com/BkwG0cZ.png>',
},
{
name: 'Gather Information',
skill: 'Diplomacy',
action: game.pf2e.actions.gatherInformation,
icon: 'icons/skills/social/diplomacy-handshake.webp'
},
{
name: 'Grapple',
skill: 'Athletics',
action: game.pf2e.actions.grapple,
icon: 'icons/skills/melee/unarmed-punch-fist.webp',
},
{
name: 'Hide',
skill: 'Stealth',
action: game.pf2e.actions.hide,
icon: 'icons/magic/nature/stealth-hide-eyes-green.webp',
},
{
name: 'Impersonate',
skill: 'Deception',
action: game.pf2e.actions.impersonate,
},
{
name: 'Jump - High',
skill: 'Athletics',
action: game.pf2e.actions.highJump,
icon: '<https://i.imgur.com/Ot4sbDt.png>',
},
{
name: 'Jump - Long',
skill: 'Athletics',
action: game.pf2e.actions.longJump,
icon: '<https://i.imgur.com/Ot4sbDt.png>',
},
{
name: 'Lie',
skill: 'Deception',
action: game.pf2e.actions.lie,
icon: 'icons/magic/control/mouth-smile-deception-purple.webp',
},
{
name: 'Make an Impression',
skill: 'Diplomacy',
action: game.pf2e.actions.makeAnImpression,
icon: 'icons/environment/people/commoner.webp',
},
{
name: 'Maneuver in Flight',
skill: 'Acrobatics',
action: game.pf2e.actions.maneuverInFlight,
icon: 'icons/commodities/biological/wing-bird-white.webp',
},
{
name: 'Pick a Lock',
skill: 'Thievery',
action: game.pf2e.actions.pickALock,
icon: '<https://i.imgur.com/cb7ejb2.png>',
},
{
name: 'Raise a Shield',
skill: '',
action: game.pf2e.actions.raiseAShield,
icon: 'systems/pf2e/icons/actions/raise-a-shield.webp',
},
{
name: 'Recall Knowledge',
skill: '',
action: 'EBdLzeLZyUju18Af',
icon: 'systems/pf2e/icons/features/classes/imperial.webp'
},
{
name: 'Repair',
skill: 'Crafting',
action: game.pf2e.actions.repair,
icon: '<https://i.imgur.com/McuTUGb.png>',
},
{
name: 'Request',
skill: 'Diplomacy',
action: game.pf2e.actions.request,
icon: 'icons/skills/social/thumbsup-approval-like.webp',
},
{
name: 'Seek',
skill: 'Perception',
action: game.pf2e.actions.seek,
icon: '<https://i.imgur.com/7mTBOrS.png>',
},
{
name: 'Sense Direction',
skill: 'Survival',
action: game.pf2e.actions.senseDirection,
icon: 'icons/tools/navigation/compass-brass-blue-red.webp',
},
{
name: 'Sense Motive',
skill: 'Perception',
action: game.pf2e.actions.senseMotive,
icon: '<https://i.imgur.com/7mTBOrS.png>',
},
{
name: 'Shove',
skill: 'Athletics',
action: game.pf2e.actions.shove,
icon: '<https://i.imgur.com/Expmoz8.png>'
},
{
name: 'Sneak',
skill: 'Stealth',
action: game.pf2e.actions.sneak,
icon: 'icons/magic/nature/stealth-hide-eyes-green.webp'
},
{
name: 'Squeeze',
skill: 'Acrobatics',
action: game.pf2e.actions.squeeze,
icon: '<https://i.imgur.com/sQ6eUA4.png>'
},
{
name: 'Swim',
skill: 'Athletics',
action: game.pf2e.actions.swim,
icon: 'icons/creatures/fish/fish-shark-swimming.webp',
},
{
name: 'Take Cover',
skill: '',
action: 'PC0y5A6tHrtJhqVL',
icon: '<https://i.imgur.com/5TgWEZF.png>',
},
{
name: 'Track',
skill: 'Survival',
action: game.pf2e.actions.track,
icon: '<https://i.imgur.com/RgHVtlC.png>',
},
{
name: 'Treat Disease',
skill: 'Medicine',
action: game.pf2e.actions.treatDisease,
icon: 'icons/magic/nature/root-vine-caduceus-healing.webp',
},
{
name: 'Treat Poison',
skill: 'Medicine',
action: game.pf2e.actions.treatPoison,
icon: 'systems/pf2e/icons/effects/treat-poison.webp',
},
{
name: 'Treat Wounds',
skill: 'Medicine',
action: 'BO2iFo14pFYHEIGa',
icon: 'icons/magic/nature/root-vine-caduceus-healing.webp',
},
{
name: 'Trip',
skill: 'Athletics',
action: game.pf2e.actions.trip,
icon: '<https://i.imgur.com/Sr2Gu19.png>',
},
{
name: 'Tumble Through',
skill: 'Acrobatics',
action: game.pf2e.actions.tumbleThrough,
icon: '<https://i.imgur.com/33UF9Ok.png>',
},
].sort((a, b) => -(a.name < b.name));
const getSkills = (actor) => {
//@ts-ignore
return { perception: actor.attributes.perception, ...actor.skills };
};
const signedNumber = (n) => {
return n < 0 ? '' + n : '+' + n;
};
const tooltip = 'You are the best in your party';
const getBestBonuses = () => {
for (const actorId of party) {
const actor = game.actors.get(actorId);
for (const action of actionList) {
const skill = getSkills(actor)[action.skill?.toLowerCase()];
if (!skill) continue;
const bonus = skill.check?.mod ?? skill.totalModifier;
if (bonus > (action.best ?? -1)) action.best = bonus;
}
}
};
if (party.includes(actor.id)) getBestBonuses();
const createButton = (action, idx) => {
//@ts-ignore
const skill = getSkills(actor)[action.skill?.toLowerCase()];
const rank = skill?.rank ?? 0;
const bonus = skill ? skill.check?.mod ?? skill.totalModifier : -1;
const best = party.length && party.includes(actor.id) ? bonus >= (action.best ?? 0) : false;
return `<button class="action-btn ${best ? 'glow' : ''}" data-action="${idx}" style="background:${colorPallete[rank]}"
${best ? `data-tooltip="${tooltip}"` : ''}>
<img src="${action.icon ?? defaultIcon}" height="24"/>${action.name} ${skill ? '(' + signedNumber(bonus) + ')' : ''}</button>`;
};
const columns = 1 + ~~((actionList.length - 1) / 14);
const width = 264 * columns;
const height = 30 + ~~((32 * actionList.length + 1) / columns);
const content = `
<style>
.pf2e-bg .window-content {
background: url(../systems/pf2e/assets/sheet/background.webp);
}
.action-list {
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin-bottom: 8px;
max-height: ${height}px;
}
.action-btn {
margin: 1px auto;
width: 250px;
height: fit-content;
box-shadow: inset 0 0 0 1px rgb(0 0 0 / 50%);
text-shadow: none;
border: #000;
color: #fff;
display: flex;
align-items: center;
}
.action-btn img {
margin-right: 5px;
}
.action-btn:hover {
text-shadow: 0 0 2px #fff;
}
.action-list button.glow {
--color-glow1: 35;
--color-glow2: 50;
animation: glow2 alternate infinite 2s;
z-index: 1;
}
@keyframes glow2 {
0% {
color: hsl(var(--color-glow2), 90%, 50%);
/*border: 1px solid hsl(var(--color-glow2), 100%, 50%);*/
/*background: hsla(var(--color-glow2), 70%, 30%, 50%);*/
box-shadow: 0 0 2px 2px hsl(var(--color-glow2), 100%, 50%);
}
100% {
color: hsl(var(--color-glow2), 90%, 50%);
/*border: 1px solid hsl(var(--color-glow2), 100%, 50%);*/
/*background: hsla(var(--color-glow2), 70%, 30%, 100%);*/
box-shadow: 0 0 2px 2px hsl(var(--color-glow2), 100%, 50%);
}
}
</style>
<div class="action-list">
${actionList.map((action, idx) => createButton(action, idx)).join('')}
</div>
`;
window.actionDialog = new Dialog(
{
title: `Actions (${actor.name})`,
content,
buttons: {
close: {
icon: `<i class="fas fa-times"></i>`,
label: 'Cancel',
},
},
default: 'close',
render: (html) => {
const action = (button) => {
const idx = button.dataset.action;
const action = actionList[idx];
if (typeof action.action === 'string') game.macros.get(action.action)?.execute();
else action.action({ actors: [actor] });
};
html.querySelectorAll('.action-list button').forEach((button) => button.addEventListener('click', () => action(button)));
},
},
{ jQuery: false, width, classes: ['pf2e-bg'] }
).render(true);
/*
Welcome to the Conditions Manager.
This macro was designed to have a window that can be minimized when not needed or opened whenever to manage conditions.
It is mostly intended for those who want an easy to read condition manager without the need for modules.
All conditions are toggled on and off by having the tokens selected that you would apply the condition to and clicking the
name of the condition. If the condition can have a value, simply click the + to increase the value or the - to decrease.
The Clear a condition button is used to clear a specific condition off of a group of selected tokens.
This macro is loosely adapted from the Apply Conditions macro created by cepvep.
CSS design by websterguy
*/
const condition_list = [
"blinded",
"broken",
"clumsy",
"concealed",
"confused",
"controlled",
"dazzled",
"deafened",
"doomed",
"drained",
"dying",
"encumbered",
"enfeebled",
"fascinated",
"fatigued",
"flat-footed",
"fleeing",
"frightened",
"grabbed",
"hidden",
"immobilized",
"invisible",
"paralyzed",
"persistent-damage",
"petrified",
"prone",
"quickened",
"restrained",
"sickened",
"slowed",
"stunned",
"stupefied",
"unconscious",
"wounded",
"undetected"
];
const wV = [
"clumsy",
"doomed",
"drained",
"dying",
"enfeebled",
"frightened",
"sickened",
"slowed",
"stunned",
"stupefied",
"wounded",
];
const nF = [
];
const script1 = async function CToggle(c) {
for(const token of canvas.tokens.controlled) {
if (token.actor === null) { ui.notifications.warn(`${token.name} does not have an actor or is broken`); continue; }
if (token.actor.itemTypes.condition.some(p => p.slug === c && p.system.references.parent !== undefined)) { continue; }
if (token.actor !== null) { await token.actor.toggleCondition(c); }
}
};
const script2 = async function ICon(c) {
for(const token of canvas.tokens.controlled) {
if (token.actor === null) { ui.notifications.warn(`${token.name} does not have an actor or is broken`); continue; }
if (token.actor !== null) { await token.actor.increaseCondition(c); }
}
};
const script3 = async function DCon(c) {
for(const token of canvas.tokens.controlled) {
if (token.actor === null) { ui.notifications.warn(`${token.name} does not have an actor or is broken`); continue; }
if (token.actor !== null) { await token.actor.decreaseCondition(c); }
}
};
const script4 = async function CCon() {
async function Clear(html) {
const c = html.find("#condition")[0].value;
for(const token of canvas.tokens.controlled) {
if (token.actor === null) { ui.notifications.warn(`${token.name} does not have an actor or is broken`); continue; }
if ( token.actor.itemTypes.effect.some(f => f.name === c) ) { (await token.actor.itemTypes.effect.find(n => n.name === c)).delete(); continue; }
if (!token.actor.itemTypes.condition.some(p => p.slug === c) || token.actor.itemTypes.condition.some(p => p.slug === c && p.system.references.parent !== undefined)) { continue; }
if (token.actor !== null) { await token.actor.toggleCondition(c); }
}
}
const cons = [];
canvas.tokens.controlled.forEach( t=> {
if (game.modules.has("pf2e-persistent-damage") && game.modules.get("pf2e-persistent-damage").active) {
t.actor.itemTypes.effect.forEach( e => {
if (e.slug.includes("persistent-damage")) { cons.push(e.name) }
});
}
t.actor.itemTypes.condition.forEach ( c => {
if (c.system.references.parent !== undefined) { return; }
cons.push(c.slug);
});
});
const ccon = [...new Set(cons)].sort();
if (ccon.length === 0) { return ui.notifications.info("No conditions to clear at this time.") }
if (ccon.length === 1) {
for(const token of canvas.tokens.controlled) {
if(token.actor.hasCondition(ccon[0])) {
await token.actor.toggleCondition(ccon[0]);
}
if(token.actor.itemTypes.effect.find(n => n.name === ccon[0]) && game.modules.has("pf2e-persistent-damage") && game.modules.get("pf2e-persistent-damage").active) { (await token.actor.itemTypes.effect.find(n => n.name === ccon[0])).delete(); }
}
}
else{
let choices = '';
ccon.forEach( cc => { choices += `<option value="${cc}">${cc[0].toUpperCase() + cc.substring(1)}</option>`; });
new Dialog({
title: "Condition to clear from tokens",
content: `<p>Choose condition to clear from selected tokens: <select id="condition">${choices}</select>`,
buttons: {
ok: {
label: "Ok",
callback: (html) => {
Clear(html);
},
},
cancel: {
label: "Cancel",
},
},
default: "cancel",
}).render(true);
}
}
let content = `
<style>
.cond-cont {
margin: 1px auto;
column-count: 3;
column-width: 100px;
background: url(systems/pf2e/assets/sheet/background.webp);
}
.cond-buttons-pd, .cond-buttons-pd:focus {
margin: 1px auto;
width: 70%;
height: fit-content;
background: var(--secondary);
box-shadow: inset 0 0 0 1px rgb(0 0 0 / 50%);
border: #000;
color: #fff;
}
.cond-buttons, .cond-buttons:focus {
margin: 1px auto;
width: 70%;
height: fit-content;
background: var(--secondary);
box-shadow: inset 0 0 0 1px rgb(0 0 0 / 50%);
text-shadow: none;
border: #000;
color: #fff;
}
.cond-buttons-small, .cond-buttons-small:focus {
margin: 1px;
width: 13%;
}
.cond-buttons:hover {
background-color:var(--secondary);
text-shadow: 0 0 2px #fff;
}
.cond-buttons-small:hover {
background-color:var(--secondary);
text-shadow: 0 0 2px #fff;
}
.cond-buttons-pd:hover {
background-color:var(--secondary);
text-shadow: 0 0 2px #fff;
}
</style><script>${script1}${script2}${script3}${script4}</script><div class="cond-cont">`
condition_list.forEach((c,i) => {
if (wV.includes(c)) {
content += `<div class="cond-butt-set"><button name="button${i}" class="cond-buttons ${i}" type="button" onclick="CToggle('${c}')">${c[0].toUpperCase() + c.substring(1)}</button><button name="button${i}+" class="cond-buttons cond-buttons-small ${i}+" type="button" onclick="ICon('${c}')">+</button><button name="button${i}-" class="cond-buttons cond-buttons-small ${i}-" type="button" onclick="DCon('${c}')">-</button></div> `;
}
else {
if ( c === "persistent-damage" ) {
if (game.modules.has("pf2e-persistent-damage") && game.modules.get("pf2e-persistent-damage").active) {
content += `<button name="button${i}" class="cond-buttons-pd" type="button" onclick="PF2EPersistentDamage.showDialog()">Persistent Damage</button> `
}
else {
content += `<button name="button${i}" class="cond-buttons-pd" type="button" onclick="CToggle('${c}')">Persistent Damage</button> `
}
}
else{
content += `<button name="button${i}" class="cond-buttons ${i}" type="button" onclick="CToggle('${c}')">${c[0].toUpperCase() + c.substring(1)}</button> `;
}
}
});
content += `<button name="button-clear" class="cond-buttons clear" type="button" onclick="CCon()">Clear a condition</button></div>`;
await new Promise(async (resolve) => {
setTimeout(resolve,200);
await new Dialog({
title:"Conditions Manager",
content,
buttons:{ Close: { label: "Close" } },
},{width: 600}).render(true);
});
/* # source "<https://gitlab.com/symonsch/my-foundryvtt-macros/-/tree/main/PF2e/Conditions> Manager.js" - Fetched on 2022-10-22T16:21:16.272Z */
function CheckFeat(slug) {
if (token.actor.items.find((i) => i.data.data.slug === slug && i.type === "feat")) {
return true;
}
return false;
}
const rollTreatWounds = async ({ DC, bonus, med, riskysurgery, mortalhealing }) => {
const options = actor.getRollOptions(["all", "skill-check", "medicine"]);
options.push("treat wounds");
options.push("action:treat-wounds");
const dc = {
value: DC,
visibility: "all",
};
if (riskysurgery || mortalhealing) {
dc.modifiers = {
success: "one-degree-better",
};
}
if (riskysurgery) {
options.push("risky-surgery");
}
med.roll({
dc: dc,
event: event,
options: options,
callback: async (roll) => {
let healFormula, successLabel;
const magicHands = CheckFeat("magic-hands");
const bonusString = bonus > 0 ? `+ ${bonus}` : "";
if (roll.data.degreeOfSuccess === 3) {
healFormula = magicHands ? `32${bonusString}` : `4d8${bonusString}`;
successLabel = "Critical Success";
} else if (roll.data.degreeOfSuccess === 2) {
healFormula = magicHands ? `16${bonusString}` : `2d8${bonusString}`;
successLabel = "Success";
} else if (roll.data.degreeOfSuccess === 1) {
successLabel = "Failure";
} else if (roll.data.degreeOfSuccess === 0) {
healFormula = "1d8";
successLabel = "Critical Failure";
}
if (riskysurgery) {
ChatMessage.create({
user: game.user.id,
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
flavor: `<strong>Damage Roll: Risky Surgery</strong>`,
roll: await new Roll("{1d8}[slashing]").roll({ async: true }),
speaker: ChatMessage.getSpeaker(),
});
}
if (healFormula !== undefined) {
const healRoll = await new Roll(`{${healFormula}}[healing]`).roll({ async: true });
const rollType = roll.data.degreeOfSuccess > 1 ? "Healing" : "Damage";
ChatMessage.create({
user: game.user.id,
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
flavor: `<strong>${rollType} Roll: Treat Wounds</strong> (${successLabel})`,
roll: healRoll,
speaker: ChatMessage.getSpeaker(),
});
}
},
});
ChatMessage.create({
content: `After the Treat Wounds attempt, assign @Item[bFT7UWGGf6JY8uK5]{1 Hour} or @Item[yZcKc8Q7ZBcRQAZf]{10 Minutes} Immunity to the recipient of the treatment.`
});
};
async function applyChanges($html) {
for (const token of canvas.tokens.controlled) {
var med = token.actor.data.data.skills.med;
if (!med) {
ui.notifications.warn(`Token ${token.name} does not have the medicine skill`);
continue;
}
const { name } = token;
const mod = parseInt($html.find('[name="modifier"]').val()) || 0;
const requestedProf = parseInt($html.find('[name="dc-type"]')[0].value) || 1;
const riskysurgery = $html.find('[name="risky_surgery_bool"]')[0]?.checked;
const mortalhealing = $html.find('[name="mortal_healing_bool"]')[0]?.checked;
const skill = $html.find('[name="skill"]')[0]?.value;
// Handle Rule Interpretation
if (game.user.isGM) {
await game.settings.set(
"pf2e",
"RAI.TreatWoundsAltSkills",
$html.find('[name="strict_rules"]')[0]?.checked
);
}
var usedProf = 0;
if (game.settings.get("pf2e", "RAI.TreatWoundsAltSkills")) {
if (skill === "cra") {
med = token.actor.data.data.skills["cra"];
}
if (skill === "nat") {
med = token.actor.data.data.skills["nat"];
}
usedProf = requestedProf <= med.rank ? requestedProf : med.rank;
} else {
usedProf = requestedProf <= med.rank ? requestedProf : med.rank;
if (skill === "cra") {
med = token.actor.data.data.skills["cra"];
}
if (skill === "nat") {
med = token.actor.data.data.skills["nat"];
if (usedProf === 0) {
usedProf = 1;
}
}
}
const medicBonus = CheckFeat("medic-dedication") ? (usedProf - 1) * 5 : 0;
const roll = [
() => ui.notifications.warn(`${name} is not trained in Medicine and doesn't know how to treat wounds.`),
() => rollTreatWounds({ DC: 15 + mod, bonus: 0 + medicBonus, med, riskysurgery, mortalhealing }),
() => rollTreatWounds({ DC: 20 + mod, bonus: 10 + medicBonus, med, riskysurgery, mortalhealing }),
() => rollTreatWounds({ DC: 30 + mod, bonus: 30 + medicBonus, med, riskysurgery, mortalhealing }),
() => rollTreatWounds({ DC: 40 + mod, bonus: 50 + medicBonus, med, riskysurgery, mortalhealing }),
][usedProf];
roll();
}
}
if (token === undefined) {
ui.notifications.warn("No token is selected.");
} else {
const chirurgeon = CheckFeat("chirurgeon");
const naturalMedicine = CheckFeat("natural-medicine");
const dialog = new Dialog({
title: "Treat Wounds",
content: `
<div>Select a target DC. Remember that you can't attempt a heal above your proficiency. Attempting to do so will downgrade the DC and amount healed to the highest you're capable of.</div>
<hr/>
${
chirurgeon || naturalMedicine
? `
<form>
<div class="form-group">
<label>Treat Wounds Skill:</label>
<select id="skill" name="skill">
<option value="med">Medicine</option>
${chirurgeon ? `<option value="cra">Crafting</option>` : ``}
${naturalMedicine ? `<option value="nat">Nature</option>` : ``}
</select>
</div>
</form>
`
: ``
}
<form>
<div class="form-group">
<label>Medicine DC:</label>
<select id="dc-type" name="dc-type">
<option value="1">Trained DC 15</option>
<option value="2">Expert DC 20, +10 Healing</option>
<option value="3">Master DC 30, +30 Healing</option>
<option value="4">Legendary DC 40, +50 Healing</option>
</select>
</div>
</form>
<form>
<div class="form-group">
<label>DC Modifier:</label>
<input id="modifier" name="modifier" type="number"/>
</div>
</form>
${
CheckFeat("risky-surgery")
? `<form><div class="form-group">
<label>Risky Surgery</label>
<input type="checkbox" id="risky_surgery_bool" name="risky_surgery_bool"></input>
</div></form>`
: ``
}
${
CheckFeat("mortal-healing")
? `<form><div class="form-group">
<label>Mortal Healing</label>
<input type="checkbox" id="mortal_healing_bool" name="mortal_healing_bool" checked></input>
</div></form>`
: ``
}
${
game.user.isGM
? `<form><div class="form-group">
<label>Allow higher DC from alternate skills?</label>
<input type="checkbox" id="strict_rules" name="strict_rules"` +
(game.settings.get("pf2e", "RAI.TreatWoundsAltSkills") ? ` checked` : ``) +
`></input>
</div></form>`
: ``
}
</form>
`,
buttons: {
yes: {
icon: `<i class="fas fa-hand-holding-medical"></i>`,
label: "Treat Wounds",
callback: applyChanges,
},
no: {
icon: `<i class="fas fa-times"></i>`,
label: "Cancel",
},
},
default: "yes",
});
dialog.render(true);
}