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 -3Ignore whitespace
public/scripts/macros/definitions/core-macros.js+10 -2
@@ -342,7 +342,12 @@ export function registerCoreMacros() {
342 MacroRegistry.registerMacro('pick', {342 MacroRegistry.registerMacro('pick', {
343 category: MacroCategory.RANDOM,343 category: MacroCategory.RANDOM,
344 list: true,344 list: true,
345 description: 'Picks a random item from a list, but keeps the choice stable for a given chat and macro position.',345 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 // `,
346 returns: 'Stable randomly selected item from the list.',351 returns: 'Stable randomly selected item from the list.',
347 exampleUsage: ['{{pick::blonde::brown::red::black::blue}}'],352 exampleUsage: ['{{pick::blonde::brown::red::black::blue}}'],
348 handler: ({ list, globalOffset, env }) => {353 handler: ({ list, globalOffset, env }) => {
@@ -369,7 +374,10 @@ export function registerCoreMacros() {
369 // nested inside arguments or scoped content374 // nested inside arguments or scoped content
370 const offset = globalOffset;375 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('-');
373 const finalSeed = getStringHash(combinedSeedString);381 const finalSeed = getStringHash(combinedSeedString);
374 const rng = seedrandom(String(finalSeed));382 const rng = seedrandom(String(finalSeed));
375 const randomIndex = Math.floor(rng() * list.length);383 const randomIndex = Math.floor(rng() * list.length);
public/scripts/slash-commands.js+42 -0
@@ -3103,6 +3103,48 @@ export function initDefaultSlashCommands() {
3103 },3103 },
3104 }));3104 }));
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
3106 registerVariableCommands();3148 registerVariableCommands();
3107}3149}
31083150
tests/frontend/MacroEngine.e2e.js+2 -1
@@ -664,7 +664,8 @@ test.describe('MacroEngine', () => {
664 const chatIdHash = chat_metadata.chat_id_hash ?? 0;664 const chatIdHash = chat_metadata.chat_id_hash ?? 0;
665 const rawContentHash = env.contentHash;665 const rawContentHash = env.contentHash;
666 const offset = globalOffset;666 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('-');
668 // Return both the seed and what would be picked for validation669 // Return both the seed and what would be picked for validation
669 const finalSeed = getStringHash(combinedSeedString);670 const finalSeed = getStringHash(combinedSeedString);
670 const rng = seedrandom(String(finalSeed));671 const rng = seedrandom(String(finalSeed));