Add `/reroll-pick` command to reset `{{pick}}` macro (#5049) * feat(macros): Add `/reroll-pick` command to reset `{{pick}}` macro choices - Added `/reroll-pick` slash command to change the seed for all `{{pick}}` macros in current chat - Command accepts optional numeric seed value, otherwise increments current seed by 1 - Updated `{{pick}}` macro to use `pick_reroll_seed` from chat metadata in seed calculation - Updated `{{pick}}` macro description to mention reroll capability - Added comprehensive help text with examples for `/reroll-pick` command - Updated tests * lint fix * fix(macros): exclude null reroll seed from {{pick}} hash calculation - Changed reroll seed default from 0 to null when not set - Filter out null values from combined seed string to avoid including "-0" suffix - Updated both core macro implementation and tests to match new behavior * Use strict null comparison --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

26d495f45705f264d4c2aa5bc26d5964d349d5d4

Wolfsblvt <wolfsblvt@gmail.com>

Signed
3 files changed, +54 -3Showing whitespace changes
public/scripts/macros/definitions/core-macros.js+10 -2
@@ -342,7 +342,12 @@ export function registerCoreMacros() {
342342 MacroRegistry.registerMacro('pick', {
343343 category: MacroCategory.RANDOM,
344344 list: true,
345345 description: 'Picks a random item from a list, but keeps the choice stable for a given chat and macro position. Can be rerolled via /reroll-pick slash command.',
346+ // TODO: add expanded documentation once HTML details are supported
347+ // descriptionDetails: `
348+ // <p>Picks a random item from a list, but keeps the choice stable for a given chat and macro position.</p>
349+ // <p>The choice can be reset per chat using the <code>/reroll-pick</code> slash command.</p>
350+ // `,
346351 returns: 'Stable randomly selected item from the list.',
347352 exampleUsage: ['{{pick::blonde::brown::red::black::blue}}'],
348353 handler: ({ list, globalOffset, env }) => {
@@ -369,7 +374,10 @@ export function registerCoreMacros() {
369374 // nested inside arguments or scoped content
370375 const offset = globalOffset;
371376
372- const combinedSeedString = `${chatIdHash}-${rawContentHash}-${offset}`;
377+ // Reroll seed allows users to reset all picks in the chat via /reroll-pick command
378+ const rerollSeed = chat_metadata.pick_reroll_seed || null;
379+
380+ const combinedSeedString = [chatIdHash, rawContentHash, offset, rerollSeed].filter(it => it !== null).join('-');
373381 const finalSeed = getStringHash(combinedSeedString);
374382 const rng = seedrandom(String(finalSeed));
375383 const randomIndex = Math.floor(rng() * list.length);
public/scripts/slash-commands.js+42 -0
@@ -3103,6 +3103,48 @@ export function initDefaultSlashCommands() {
31033103 },
31043104 }));
31053105
3106+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
3107+ name: 'reroll-pick',
3108+ callback: (_, value) => {
3109+ const currentSeed = chat_metadata.pick_reroll_seed ?? 0;
3110+ const parsedValue = value ? parseInt(String(value), 10) : NaN;
3111+
3112+ if (!isNaN(parsedValue)) {
3113+ chat_metadata.pick_reroll_seed = parsedValue;
3114+ } else {
3115+ chat_metadata.pick_reroll_seed = currentSeed + 1;
3116+ }
3117+
3118+ saveMetadataDebounced();
3119+ return String(chat_metadata.pick_reroll_seed);
3120+ },
3121+ returns: t`The new reroll seed value.`,
3122+ unnamedArgumentList: [
3123+ SlashCommandArgument.fromProps({
3124+ description: t`Optional seed value to set. If not provided, increments current seed by 1.`,
3125+ typeList: [ARGUMENT_TYPE.NUMBER],
3126+ }),
3127+ ],
3128+ helpString: `
3129+ <div>
3130+ ${t`Rerolls all <code>{{pick}}</code> macro choices in the current chat.`}
3131+ </div>
3132+ <div>
3133+ ${t`The <code>{{pick}}</code> macro normally keeps stable choices per chat. This command changes the seed used for all picks, causing them to resolve to (possibly) different values.`}
3134+ </div>
3135+ <div>
3136+ ${t`If a number is provided, sets the seed to that value. Otherwise, increments the current seed by 1.`}
3137+ </div>
3138+ <div>
3139+ <strong>${t`Example:`}</strong>
3140+ <ul>
3141+ <li><pre><code>/reroll-pick</code></pre> ${t`Increments the seed by 1.`}</li>
3142+ <li><pre><code>/reroll-pick 5</code></pre> ${t`Sets the seed to 5.`}</li>
3143+ </ul>
3144+ </div>
3145+ `,
3146+ }));
3147+
31063148 registerVariableCommands();
31073149}
31083150
tests/frontend/MacroEngine.e2e.js+2 -1
@@ -664,7 +664,8 @@ test.describe('MacroEngine', () => {
664664 const chatIdHash = chat_metadata.chat_id_hash ?? 0;
665665 const rawContentHash = env.contentHash;
666666 const offset = globalOffset;
667- const combinedSeedString = `${chatIdHash}-${rawContentHash}-${offset}`;
667+ const rerollSeed = chat_metadata.pick_reroll_seed || null;
668+ const combinedSeedString = [chatIdHash, rawContentHash, offset, rerollSeed].filter(it => it !== null).join('-');
668669 // Return both the seed and what would be picked for validation
669670 const finalSeed = getStringHash(combinedSeedString);
670671 const rng = seedrandom(String(finalSeed));