Files
Foundry-VTT-Docker/resources/app/client/data/documents/macro.js
2025-01-04 00:34:03 +01:00

154 lines
5.8 KiB
JavaScript

/**
* The client-side Macro document which extends the common BaseMacro model.
* @extends foundry.documents.BaseMacro
* @mixes ClientDocumentMixin
*
* @see {@link Macros} The world-level collection of Macro documents
* @see {@link MacroConfig} The Macro configuration application
*/
class Macro extends ClientDocumentMixin(foundry.documents.BaseMacro) {
/* -------------------------------------------- */
/* Model Properties */
/* -------------------------------------------- */
/**
* Is the current User the author of this macro?
* @type {boolean}
*/
get isAuthor() {
return game.user === this.author;
}
/* -------------------------------------------- */
/**
* Test whether the current User is capable of executing this Macro.
* @type {boolean}
*/
get canExecute() {
return this.canUserExecute(game.user);
}
/* -------------------------------------------- */
/**
* Provide a thumbnail image path used to represent this document.
* @type {string}
*/
get thumbnail() {
return this.img;
}
/* -------------------------------------------- */
/* Model Methods */
/* -------------------------------------------- */
/**
* Test whether the given User is capable of executing this Macro.
* @param {User} user The User to test.
* @returns {boolean} Can this User execute this Macro?
*/
canUserExecute(user) {
if ( !this.testUserPermission(user, "LIMITED") ) return false;
return this.type === "script" ? user.can("MACRO_SCRIPT") : true;
}
/* -------------------------------------------- */
/**
* Execute the Macro command.
* @param {object} [scope={}] Macro execution scope which is passed to script macros
* @param {ChatSpeakerData} [scope.speaker] The speaker data
* @param {Actor} [scope.actor] An Actor who is the protagonist of the executed action
* @param {Token} [scope.token] A Token which is the protagonist of the executed action
* @param {Event|RegionEvent} [scope.event] An optional event passed to the executed macro
* @returns {Promise<unknown>|void} A promising containing a created {@link ChatMessage} (or `undefined`) if a chat
* macro or the return value if a script macro. A void return is possible if the user
* is not permitted to execute macros or a script macro execution fails.
*/
execute(scope={}) {
if ( !this.canExecute ) {
ui.notifications.warn(`You do not have permission to execute Macro "${this.name}".`);
return;
}
switch ( this.type ) {
case "chat":
return this.#executeChat(scope.speaker);
case "script":
if ( foundry.utils.getType(scope) !== "Object" ) {
throw new Error("Invalid scope parameter passed to Macro#execute which must be an object");
}
return this.#executeScript(scope);
}
}
/* -------------------------------------------- */
/**
* Execute the command as a chat macro.
* Chat macros simulate the process of the command being entered into the Chat Log input textarea.
* @param {ChatSpeakerData} [speaker] The speaker data
* @returns {Promise<ChatMessage|void>} A promising that resolves to either a created chat message or void in case an
* error is thrown or the message's creation is prevented by some other means
* (e.g., a hook).
*/
#executeChat(speaker) {
return ui.chat.processMessage(this.command, {speaker}).catch(err => {
Hooks.onError("Macro#_executeChat", err, {
msg: "There was an error in your chat message syntax.",
log: "error",
notify: "error",
command: this.command
});
});
}
/* -------------------------------------------- */
/**
* Execute the command as a script macro.
* Script Macros are wrapped in an async IIFE to allow the use of asynchronous commands and await statements.
* @param {object} [scope={}] Macro execution scope which is passed to script macros
* @param {ChatSpeakerData} [scope.speaker] The speaker data
* @param {Actor} [scope.actor] An Actor who is the protagonist of the executed action
* @param {Token} [scope.token] A Token which is the protagonist of the executed action
* @returns {Promise<unknown>|void} A promise containing the return value of the macro, if any, or nothing if the
* macro execution throws an error.
*/
#executeScript({speaker, actor, token, ...scope}={}) {
// Add variables to the evaluation scope
speaker = speaker || ChatMessage.implementation.getSpeaker({actor, token});
const character = game.user.character;
token = token || (canvas.ready ? canvas.tokens.get(speaker.token) : null) || null;
actor = actor || token?.actor || game.actors.get(speaker.actor) || null;
// Unpack argument names and values
const argNames = Object.keys(scope);
if ( argNames.some(k => Number.isNumeric(k)) ) {
throw new Error("Illegal numeric Macro parameter passed to execution scope.");
}
const argValues = Object.values(scope);
// Define an AsyncFunction that wraps the macro content
// eslint-disable-next-line no-new-func
const fn = new foundry.utils.AsyncFunction("speaker", "actor", "token", "character", "scope", ...argNames,
`{${this.command}\n}`);
// Attempt macro execution
try {
return fn.call(this, speaker, actor, token, character, scope, ...argValues);
} catch(err) {
ui.notifications.error("MACRO.Error", { localize: true });
}
}
/* -------------------------------------------- */
/** @inheritdoc */
_onClickDocumentLink(event) {
return this.execute({event});
}
}