|
/**
|
|
* Registers core commands for autocompletion.
|
|
* @private
|
|
*/
|
|
export const registerCoreCommands = function () {
|
|
if (!game.settings.get("_chatcommands", "includeCoreCommands")) return;
|
|
|
|
const commands = game.chatCommands;
|
|
registerMessageCommands(commands);
|
|
registerRollCommands(commands);
|
|
}
|
|
|
|
/**
|
|
* Registers core commands for modifying messages.
|
|
* @param {ChatCommands} commands The game's chat command API instance.
|
|
* @private
|
|
*/
|
|
function registerMessageCommands(commands) {
|
|
commands.register({
|
|
name: "/ic",
|
|
module: "core",
|
|
icon: "<i class='fas fa-id-badge'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.ic")
|
|
});
|
|
commands.register({
|
|
name: "/ooc",
|
|
module: "core",
|
|
icon: "<i class='fas fa-chalkboard-user'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.ooc")
|
|
});
|
|
commands.register({
|
|
name: "/emote",
|
|
module: "core",
|
|
aliases: ["/em", "/me"],
|
|
icon: "<i class='fas fa-address-card'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.emote")
|
|
});
|
|
commands.register({
|
|
name: "/whisper",
|
|
module: "core",
|
|
aliases: ["/w", "@"],
|
|
icon: "<i class='fas fa-message'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.whisper.description"),
|
|
autocompleteCallback: game.modules.get("autocomplete-whisper")?.active ? () => [] : completeWhisper
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates entries for completing user names and suggests bracket syntax as needed.
|
|
* @param {AutocompleteMenu} menu The menu that initiated the completion process.
|
|
* @param {string} alias The alias of the command.
|
|
* @param {string} parameters The parameters of the command.
|
|
* @returns {HTMLElement[]} The HTML elements containing syntax and recent roll entries.
|
|
* @private
|
|
*/
|
|
function completeWhisper(menu, alias, parameters) {
|
|
let userNames = ["GM", "Players"].concat(Array.from(game.users.values()).map(u => u.name));
|
|
const included = [];
|
|
const suggested = [];
|
|
const candidates = parameters.split(',');
|
|
let match = false;
|
|
for (let i = 0; i < candidates.length; i++) {
|
|
// Strip whitespace, brackets and capitalization.
|
|
const name = candidates[i].toLowerCase().replace(/^[\s\[]*|[\s\]]*$/g, "");
|
|
match = false;
|
|
|
|
// Check all users that haven't already been added.
|
|
for (let userName of userNames.filter(n => !included.includes(n))) {
|
|
if (userName.toLowerCase() === name) {
|
|
// Exact match, include the user.
|
|
match = true;
|
|
included.push(userName);
|
|
break;
|
|
} else if (i === candidates.length - 1 && (name === "" || userName.toLowerCase().includes(name))) {
|
|
// Last entry may not have exact match, suggest the user if the name could match the input.
|
|
match = true;
|
|
suggested.push(userName);
|
|
}
|
|
}
|
|
|
|
// Stop searching if any input doesn't have a match (which likely means a comma within the message).
|
|
if (!match) break;
|
|
}
|
|
|
|
// Not all candidates have a match, close the menu.
|
|
if (!match) return [];
|
|
|
|
// There are no suggestions, check if the paramters still need syntax adjustments.
|
|
const needsRedirect = alias === "@";
|
|
if (!suggested.length) {
|
|
const needsBrackets = included.length > 1 || included[0]?.includes(' ');
|
|
const hasBrackets = parameters.startsWith('[') && parameters.trimEnd().endsWith(']');
|
|
if (included.length && (needsRedirect || (hasBrackets !== needsBrackets))) {
|
|
// Suggest /w without displaying a menu.
|
|
menu.suggest(needsBrackets ? `/w [${included.join(", ")}]` : "/w " + included.join(", "));
|
|
}
|
|
return [];
|
|
}
|
|
|
|
let prefix = needsRedirect ? "/w " : alias + " "; // Redirect @ to /w
|
|
if (included.length) {
|
|
// There already is a user in the parameters, suggest multi user syntax.
|
|
return suggested.map(n => createUserElement(`${prefix}[${included.join(", ")}, ${n}]`, n));
|
|
} else {
|
|
// We don't have a user yet, suggest single user syntax.
|
|
return suggested.map(n => {
|
|
const completedName = n.includes(' ') ? `[${n}]` : n; // Handle user names with spaces.
|
|
return createUserElement(prefix + completedName, n);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a command element for the given user's name. This may add additional information when the name is a
|
|
* special string rather than a user's name, e.g. "GM" or "Players".
|
|
* @param {string} command The command to complete when selecting the user.
|
|
* @param {string} name The name of the user.
|
|
* @returns {HTMLElement} An HTML element containing the command entry for the user.
|
|
* @private
|
|
*/
|
|
function createUserElement(command, name) {
|
|
let content = name;
|
|
if (name === "GM") {
|
|
const gmLocal = game.i18n.localize("_chatcommands.coreCommands.whisper.gm");
|
|
if (gmLocal !== name) content += ` (${gmLocal})`;
|
|
content += ` <span class="notes">${game.users.filter(u => u.isGM).map(u => u.name).join(", ")}</span>`;
|
|
} else if (name === "Players") {
|
|
const playerLocal = game.i18n.localize("_chatcommands.coreCommands.whisper.players");
|
|
if (playerLocal !== name) content += ` (${playerLocal})`;
|
|
content += ` <span class="notes">${game.users.filter(u => !u.isGM).map(u => u.name).join(", ")}</span>`;
|
|
}
|
|
|
|
return game.chatCommands.createCommandElement(command, content);
|
|
}
|
|
|
|
/**
|
|
* Registers hooks and core commands for displaying roll syntax and recent rolls.
|
|
* @param {ChatCommands} commands The game's chat command API instance.
|
|
* @private
|
|
*/
|
|
function registerRollCommands(commands) {
|
|
// Register setting to store recent rolls per user.
|
|
game.settings.register("_chatcommands", "recentRolls", {
|
|
name: "Recent dice rolls",
|
|
scope: 'client',
|
|
config: false,
|
|
type: Array,
|
|
default: []
|
|
});
|
|
|
|
// Register hook to track recent rolls for core commands.
|
|
Hooks.on("invokeChatCommand", (_, command, parameters) => {
|
|
if (!command.name.endsWith("roll") || command.module !== "core") return;
|
|
|
|
const recentRolls = game.settings.get("_chatcommands", "recentRolls");
|
|
const existingRoll = recentRolls.indexOf(parameters);
|
|
if (existingRoll !== -1) recentRolls.splice(existingRoll, 1);
|
|
recentRolls.unshift(parameters);
|
|
game.settings.set("_chatcommands", "recentRolls", recentRolls.slice(0, 5));
|
|
});
|
|
|
|
commands.register({
|
|
name: "/roll",
|
|
module: "core",
|
|
aliases: ["/r"],
|
|
icon: "<i class='fas fa-dice'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.roll.basic"),
|
|
autocompleteCallback: completeDice,
|
|
closeOnComplete: false
|
|
});
|
|
commands.register({
|
|
name: "/gmroll",
|
|
module: "core",
|
|
aliases: ["/gmr"],
|
|
icon: "<i class='fas fa-dice-two'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.roll.gm"),
|
|
autocompleteCallback: completeDice,
|
|
closeOnComplete: false
|
|
});
|
|
commands.register({
|
|
name: "/blindroll",
|
|
module: "core",
|
|
aliases: ["/broll", "/br"],
|
|
icon: "<i class='fas fa-eye-slash'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.roll.blind"),
|
|
autocompleteCallback: completeDice,
|
|
closeOnComplete: false
|
|
});
|
|
commands.register({
|
|
name: "/selfroll",
|
|
module: "core",
|
|
aliases: ["/sr"],
|
|
icon: "<i class='fas fa-dice-one'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.roll.self"),
|
|
autocompleteCallback: completeDice,
|
|
closeOnComplete: false
|
|
});
|
|
commands.register({
|
|
name: "/publicroll",
|
|
module: "core",
|
|
aliases: ["/pr"],
|
|
icon: "<i class='fas fa-dice-five'></i>",
|
|
description: game.i18n.localize("_chatcommands.coreCommands.roll.public"),
|
|
autocompleteCallback: completeDice,
|
|
closeOnComplete: false
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a set of menu entries to display roll syntax information and suggest recent rolls. Some info entries may
|
|
* be skipped if the menu doesn't have enough space.
|
|
* @param {AutocompleteMenu} menu The menu that initiated the completion process.
|
|
* @param {string} alias The alias of the command.
|
|
* @param {string} parameters The parameters of the command.
|
|
* @returns {HTMLElement[]} The HTML elements containing syntax and recent roll entries.
|
|
* @private
|
|
*/
|
|
function completeDice(menu, alias, parameters) {
|
|
const commands = game.chatCommands;
|
|
const recentRolls = game.settings.get("_chatcommands", "recentRolls")
|
|
.slice()
|
|
.filter(r => r.includes(parameters))
|
|
.map(r => commands.createCommandElement(alias + " " + r, r));
|
|
let info;
|
|
if (menu.maxEntries >= 10) {
|
|
info = [
|
|
commands.createInfoElement(game.i18n.localize("_chatcommands.coreCommands.roll.simpleInfo")),
|
|
commands.createInfoElement(game.i18n.localize("_chatcommands.coreCommands.roll.descriptionInfo")),
|
|
commands.createInfoElement(game.i18n.localize("_chatcommands.coreCommands.roll.modifierInfo")),
|
|
commands.createInfoElement(game.i18n.localize("_chatcommands.coreCommands.roll.advancedInfo"))
|
|
];
|
|
if (!recentRolls.length) return info;
|
|
info.push(commands.createInfoElement(`<hr><p class="notes">
|
|
${game.i18n.localize("_chatcommands.coreCommands.roll.recent")}</p>`));
|
|
} else if (menu.maxEntries > 5) {
|
|
info = [commands.createInfoElement(game.i18n.localize("_chatcommands.coreCommands.roll.simpleInfo"))];
|
|
} else {
|
|
return recentRolls;
|
|
}
|
|
|
|
return info.concat(recentRolls);
|
|
}
|