Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // === Mass Actor and Token Renamer ===
- // Key used to save settings for the current user
- const SETTINGS_KEY = "macro-token-renamer-settings";
- async function main() {
- // === Pre-checks ===
- const controlled = canvas.tokens?.controlled ?? [];
- if (controlled.length === 0) {
- return ui.notifications.warn("Select at least one token first.");
- }
- const rollTables = game.tables?.contents ?? [];
- // Load previously saved settings for the user
- const savedSettings = game.user.getFlag('world', SETTINGS_KEY) ?? {};
- // Build table options using IDs (safe) and escaped labels
- const tableOptions = rollTables.map(t =>
- `<option value="${t.id}" ${savedSettings.tables?.includes(t.id) ? "selected" : ""}>${foundry.utils.escapeHTML(t.name)}</option>`
- ).join("");
- // === UI HELPER FUNCTION ===
- const updateUI = (html) => {
- // Read all form inputs
- const numTables = parseInt(html.find('#numtables').val(), 10) || 0;
- const appendWords = html.find('#appendWords').val().trim();
- const useAsTitle = html.find('#useAsTitle').is(':checked');
- const appendStart = html.find('[name="appendStart"]').is(':checked');
- const appendEnd = html.find('[name="appendEnd"]').is(':checked');
- const changeDoc = html.find('[name="changeDoc"]').is(':checked');
- const changeActor = html.find('[name="changeActor"]').is(':checked');
- // Show/hide roll table selectors
- html.find('.table-selector').hide();
- for (let i = 1; i <= numTables; i++) {
- html.find(`#table-selector-${i}`).show();
- }
- // Disable "Use as title" if tables are selected
- const noTables = numTables === 0;
- html.find('[name="useAsTitle"]').prop('disabled', !noTables);
- // Determine if other options should be disabled
- const disableAppend = appendWords.length === 0 || (noTables && useAsTitle);
- const disableKeeps = noTables && useAsTitle;
- html.find('[name="appendStart"], [name="appendEnd"]').prop('disabled', disableAppend);
- html.find('[name="keepDocName"], [name="keepActorName"]').prop('disabled', disableKeeps);
- // --- LIVE PREVIEW LOGIC ---
- const prefix = appendStart && !disableAppend ? `${appendWords} ` : "";
- const suffix = appendEnd && !disableAppend ? ` ${appendWords}` : "";
- const tablePart = noTables ? "" : Array.from({ length: numTables }, (_, i) => `[Table ${i + 1}]`).join(" ");
- const generatePreview = (original, keep) => {
- if (noTables && useAsTitle) return appendWords || "[Additional Words]";
- const core = [(keep && !disableKeeps ? `[${original}]` : ""), tablePart].filter(Boolean).join(" ");
- return `${prefix}${core}${suffix}`.trim() || "[No Change]";
- };
- let tokenPreview = generatePreview("Original Token", html.find('[name="keepDocName"]').is(':checked'));
- let actorPreview = generatePreview("Original Actor", html.find('[name="keepActorName"]').is(':checked'));
- if (!changeDoc) tokenPreview = "[No Change]";
- if (!changeActor) actorPreview = "[No Change]";
- html.find('#token-name-preview').text(tokenPreview);
- html.find('#actor-name-preview').text(actorPreview);
- // --- VALIDATION LOGIC ---
- const isInvalidState = appendWords.length > 0 && noTables && !useAsTitle && !appendStart && !appendEnd;
- const applyButton = html.closest('.dialog').find('.dialog-button.apply');
- const warningDiv = html.find('#preview-warning');
- if (isInvalidState) {
- applyButton.prop('disabled', true);
- warningDiv.text("Choose Append at Start/End, or check βUse as full nameβ.").show();
- } else {
- applyButton.prop('disabled', false);
- warningDiv.hide().text("");
- }
- };
- // === RENDER DIALOG ===
- new Dialog({
- title: "Token Rename Helper",
- content: `
- <form>
- <div style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
- <!-- Left Column -->
- <div style="flex: 2; min-width: 250px;">
- <fieldset>
- <legend>Name Source</legend>
- <div class="form-group">
- <label for="numtables">Number of Roll Tables:</label>
- <input id="numtables" name="numtables" type="number" min="0" max="${rollTables.length}" value="${savedSettings.numTables ?? 0}">
- </div>
- ${Array.from({ length: rollTables.length }, (_, i) => `
- <div class="form-group table-selector" id="table-selector-${i + 1}" style="display:none;">
- <label for="table${i + 1}">Roll Table ${i + 1}</label>
- <select id="table${i + 1}" name="table${i + 1}">${tableOptions}</select>
- </div>
- `).join("")}
- <hr/>
- <div class="form-group">
- <label for="appendWords">Additional Words:</label>
- <!-- MODIFIED: Always starts empty -->
- <input id="appendWords" name="appendWords" type="text" placeholder="e.g., Elite, of the North" value="">
- </div>
- <div class="form-group">
- <label for="useAsTitle">Use βAdditional Wordsβ as full name (if no tables)</label>
- <!-- MODIFIED: Always starts unchecked -->
- <input id="useAsTitle" name="useAsTitle" type="checkbox">
- </div>
- </fieldset>
- </div>
- <!-- Right Column -->
- <div style="flex: 1; min-width: 200px;">
- <fieldset>
- <legend>Fine-Tuning</legend>
- <div class="form-group">
- <label for="keepDocName">Keep Original Token Name</label>
- <input id="keepDocName" name="keepDocName" type="checkbox" ${savedSettings.keepDocName ? "checked" : ""}>
- </div>
- <div class="form-group">
- <label for="keepActorName">Keep Original Actor Name</label>
- <input id="keepActorName" name="keepActorName" type="checkbox" ${savedSettings.keepActorName ?? true ? "checked" : ""}>
- </div>
- <div class="form-group">
- <label for="appendStart">Append Words at Start</label>
- <!-- MODIFIED: Always starts unchecked -->
- <input id="appendStart" name="appendStart" type="checkbox">
- </div>
- <div class="form-group">
- <label for="appendEnd">Append Words at End</label>
- <!-- MODIFIED: Always starts unchecked -->
- <input id="appendEnd" name="appendEnd" type="checkbox">
- </div>
- </fieldset>
- <fieldset>
- <legend>Target</legend>
- <div class="form-group">
- <label for="changeDoc">Change Token Name</label>
- <input id="changeDoc" name="changeDoc" type="checkbox" ${savedSettings.changeDoc ?? true ? "checked" : ""}>
- </div>
- <div class="form-group">
- <label for="changeActor">Change Actor Name</label>
- <input id="changeActor" name="changeActor" type="checkbox" ${savedSettings.changeActor ?? true ? "checked" : ""}>
- </div>
- <div class="form-group">
- <label for="skipLinkedActors">Skip linked actors</label>
- <input id="skipLinkedActors" name="skipLinkedActors" type="checkbox" ${savedSettings.skipLinkedActors ?? true ? "checked" : ""}>
- </div>
- </fieldset>
- </div>
- </div>
- <hr/>
- <fieldset>
- <legend>Live Preview</legend>
- <div id="preview-warning" style="color: #c94c4c; text-align: center; display: none; margin-bottom: 5px; font-weight: bold;"></div>
- <div style="display: flex; flex-direction: column; gap: 5px; text-align: left;">
- <div>
- <strong>Token:</strong>
- <code id="token-name-preview" style="display: block; width: 100%; background: rgba(0,0,0,0.75); padding: 6px 8px; border-radius: 3px; font-size: 1.1em; box-sizing: border-box;"></code>
- </div>
- <div>
- <strong>Actor:</strong>
- <code id="actor-name-preview" style="display: block; width: 100%; background: rgba(0,0,0,0.75); padding: 6px 8px; border-radius: 3px; font-size: 1.1em; box-sizing: border-box;"></code>
- </div>
- </div>
- </fieldset>
- </form>
- `,
- buttons: {
- apply: {
- icon: "<i class='fas fa-check'></i>",
- label: "Apply Changes",
- classes: "apply",
- callback: async (html) => {
- const settings = {
- numTables: parseInt(html.find('#numtables').val(), 10) || 0,
- appendWords: html.find('#appendWords').val().trim(),
- useAsTitle: html.find('#useAsTitle').is(':checked'),
- keepDocName: html.find('#keepDocName').is(':checked'),
- keepActorName: html.find('#keepActorName').is(':checked'),
- appendStart: html.find('#appendStart').is(':checked'),
- appendEnd: html.find('#appendEnd').is(':checked'),
- changeDoc: html.find('#changeDoc').is(':checked'),
- changeActor: html.find('#changeActor').is(':checked'),
- skipLinkedActors: html.find('#skipLinkedActors').is(':checked'),
- tables: Array.from({ length: parseInt(html.find('#numtables').val(), 10) || 0 }, (_, i) => html.find(`#table${i + 1}`).val())
- };
- // Create a separate object for saving, excluding the temporary fields
- const settingsToSave = { ...settings };
- delete settingsToSave.appendWords;
- delete settingsToSave.useAsTitle;
- delete settingsToSave.appendStart;
- delete settingsToSave.appendEnd;
- await game.user.setFlag('world', SETTINGS_KEY, settingsToSave);
- await executeRename(settings, controlled);
- }
- },
- cancel: {
- icon: "<i class='fas fa-times'></i>",
- label: "Cancel"
- },
- },
- default: "apply",
- render: (html) => {
- html.find('input, select').on('change input', () => updateUI(html));
- updateUI(html);
- },
- close: () => {}
- }).render(true, { width: 600 });
- }
- // === Renaming Logic ===
- async function executeRename(settings, controlled) {
- const { numTables, appendWords, useAsTitle, keepDocName, keepActorName, appendStart, appendEnd, changeDoc, changeActor, skipLinkedActors } = settings;
- if (numTables === 0 && appendWords.length === 0) {
- return ui.notifications.warn("No roll tables or Additional Words provided.");
- }
- const linkedCount = controlled.filter(t => t.document?.actorLink).length;
- if (changeActor && linkedCount > 0) {
- if (skipLinkedActors) ui.notifications.info(`Skipping actor renames for ${linkedCount} linked token${linkedCount !== 1 ? "s" : ""}.`);
- else ui.notifications.warn("Note: Renaming a linked Actor affects all its tokens.");
- }
- const effectiveAppendStart = appendStart ? appendWords : null;
- const effectiveAppendEnd = appendEnd ? appendWords : null;
- if (numTables === 0) {
- const baseName = useAsTitle ? appendWords : "";
- for (const token of controlled) {
- const isLinked = skipLinkedActors && token.document?.actorLink;
- await renameTokenParts(token, baseName, {
- doTokenDoc: changeDoc,
- doActor: changeActor && !isLinked,
- keepTokenDoc: useAsTitle ? false : keepDocName,
- keepActor: useAsTitle ? false : keepActorName,
- appendStart: useAsTitle ? null : effectiveAppendStart,
- appendEnd: useAsTitle ? null : effectiveAppendEnd,
- });
- }
- } else {
- const tables = settings.tables.map(id => game.tables.get(id));
- if (tables.some(t => !t)) return ui.notifications.error("One or more selected roll tables could not be found.");
- for (const token of controlled) {
- const draws = await Promise.all(tables.map(tbl => tbl.draw({ displayChat: false })));
- const newName = draws.map(d => d.results?.[0]?.text ?? "").filter(Boolean).join(" ").trim();
- if (!newName) continue;
- const isLinked = skipLinkedActors && token.document?.actorLink;
- await renameTokenParts(token, newName, {
- doTokenDoc: changeDoc,
- doActor: changeActor && !isLinked,
- keepTokenDoc: keepDocName,
- keepActor: keepActorName,
- appendStart: effectiveAppendStart,
- appendEnd: effectiveAppendEnd,
- });
- }
- }
- ui.notifications.info("Renaming complete.");
- }
- async function renameTokenParts(token, newName, { doTokenDoc, doActor, keepTokenDoc, keepActor, appendStart, appendEnd }) {
- const updates = [];
- if (doTokenDoc) {
- let tokenDocName = keepTokenDoc ? `${token.document.name} ${newName}`.trim() : newName;
- if (appendStart) tokenDocName = `${appendStart} ${tokenDocName}`;
- if (appendEnd) tokenDocName = `${tokenDocName} ${appendEnd}`;
- updates.push(token.document.update({ name: tokenDocName.trim() }));
- }
- if (doActor && token.actor) {
- let actorName = keepActor ? `${token.actor.name} ${newName}`.trim() : newName;
- if (appendStart) actorName = `${appendStart} ${actorName}`;
- if (appendEnd) actorName = `${actorName} ${appendEnd}`;
- updates.push(token.actor.update({ name: actorName.trim() }));
- }
- if (updates.length > 0) await Promise.all(updates);
- }
- main();
Advertisement
Add Comment
Please, Sign In to add comment