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>
Signed| @@ -342,7 +342,12 @@ export function registerCoreMacros() { | ||
| 342 | 342 | MacroRegistry.registerMacro('pick', { |
| 343 | 343 | category: MacroCategory.RANDOM, |
| 344 | 344 | list: true, |
| 345 | 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 | 351 | returns: 'Stable randomly selected item from the list.', |
| 347 | 352 | exampleUsage: ['{{pick::blonde::brown::red::black::blue}}'], |
| 348 | 353 | handler: ({ list, globalOffset, env }) => { |
| @@ -369,7 +374,10 @@ export function registerCoreMacros() { | ||
| 369 | 374 | // nested inside arguments or scoped content |
| 370 | 375 | const offset = globalOffset; |
| 371 | 376 | |
| 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 | 381 | const finalSeed = getStringHash(combinedSeedString); |
| 374 | 382 | const rng = seedrandom(String(finalSeed)); |
| 375 | 383 | const randomIndex = Math.floor(rng() * list.length); |
| @@ -3103,6 +3103,48 @@ export function initDefaultSlashCommands() { | ||
| 3103 | 3103 | }, |
| 3104 | 3104 | })); |
| 3105 | 3105 | |
| 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 | 3148 | registerVariableCommands(); |
| 3107 | 3149 | } |
| 3108 | 3150 | |
| @@ -664,7 +664,8 @@ test.describe('MacroEngine', () => { | ||
| 664 | 664 | const chatIdHash = chat_metadata.chat_id_hash ?? 0; |
| 665 | 665 | const rawContentHash = env.contentHash; |
| 666 | 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 | 669 | // Return both the seed and what would be picked for validation |
| 669 | 670 | const finalSeed = getStringHash(combinedSeedString); |
| 670 | 671 | const rng = seedrandom(String(finalSeed)); |