Prompt the subject's controller to pick one of several author-defined
options, then run that option's own nested outcome list against the same
execution context. This is the "User Choice" meta outcome — a second
promptFor consumer alongside summonNpc/scryArrange/playAnimation,
but routed through _renderUserChoice (prompt-renderers.mjs) instead of
a bespoke Application.
Config:
promptTitle string | null — optional dialog heading; falls back to a
sensible default (referencing the subject's name) when unset.
sampleCount number — 0 (default) preserves today's behavior: prompt with
every configured option. Any value > 0 does NOT skip the
prompt — it randomly narrows options down to that many
DISTINCT entries (sampling without replacement —
Math.random(), matching this registry's existing
convention for applyDamage's type: "random" damage-type
pool) and prompts with ONLY that narrowed subset, via the
exact same promptFor/_renderUserChoice flow used when
not sampling. The human (player/GM) still makes the actual
choice, from a smaller randomly-drawn pool instead of the
full option list — this is a pre-filter on the choice SET,
never a bypass of the human prompt. This is authored at
design time (a trigger outcome config, edited once, fired
many times) — not a runtime/per-call override, matching the
0-means-"feature off" numeric-field convention already
used by changeVital's max field elsewhere in this file,
rather than a separate enable-flag-plus-count pair. If
sampleCount exceeds (or equals) the number of configured
options, it's silently capped at options.length (console
warning logged when it strictly exceeds) and the prompt
simply shows the full pool — matching this file's general
cap-with-warning handling for out-of-range counts (see
useAction's recursion-depth cap).
options Array<{
id string — foundry.utils.randomID()
name string — short label shown on the choice button
description string — longer text shown under the name
outcomes object[] — plain-object outcome entries
{ id, order, type, target, config }, same
shape as TriggerOutcomeModel fields but never
backed by an EmbeddedDataField. Deliberately
NOT nestable — a userChoice inside an
option's outcomes is rejected by the config
UI, not by this executor.
}>
Only { id, name, description } per option crosses into the prompt spec
sent to promptFor — the nested outcomes arrays never leave the GM/
executor client, since they're plain data (not sanitized for socket
transport) and only ever need to run locally once a choice comes back.
promptFor is always called — cfg.sampleCount > 0 only changes which
options are included in the spec's options array (the narrowed subset
instead of the full pool); it never skips the call itself.
On response, the single chosen option's outcomes runs through
_runOutcomeList against the same context (so NegateSignal,
context._actorUpdateBatch, and context.outcomeResults all compose with
the rest of the trigger's outcome list). Stamps
context.outcomeResults.userChoice[subject.id] = { chosenOptionId, chosenOptionName } — exactly one option is ever chosen (by the human),
regardless of whether the pool was narrowed by sampleCount first, so the
result shape stays singular.
Prompt the subject's controller to pick one of several author-defined options, then run that option's own nested outcome list against the same execution context. This is the "User Choice" meta outcome — a second
promptForconsumer alongsidesummonNpc/scryArrange/playAnimation, but routed through_renderUserChoice(prompt-renderers.mjs) instead of a bespoke Application.Config: promptTitle string | null — optional dialog heading; falls back to a sensible default (referencing the subject's name) when unset. sampleCount number — 0 (default) preserves today's behavior: prompt with every configured option. Any value > 0 does NOT skip the prompt — it randomly narrows
optionsdown to that many DISTINCT entries (sampling without replacement —Math.random(), matching this registry's existing convention forapplyDamage'stype: "random"damage-type pool) and prompts with ONLY that narrowed subset, via the exact samepromptFor/_renderUserChoiceflow used when not sampling. The human (player/GM) still makes the actual choice, from a smaller randomly-drawn pool instead of the full option list — this is a pre-filter on the choice SET, never a bypass of the human prompt. This is authored at design time (a trigger outcome config, edited once, fired many times) — not a runtime/per-call override, matching the0-means-"feature off" numeric-field convention already used bychangeVital'smaxfield elsewhere in this file, rather than a separate enable-flag-plus-count pair. IfsampleCountexceeds (or equals) the number of configured options, it's silently capped atoptions.length(console warning logged when it strictly exceeds) and the prompt simply shows the full pool — matching this file's general cap-with-warning handling for out-of-range counts (seeuseAction's recursion-depth cap). options Array<{ id string — foundry.utils.randomID() name string — short label shown on the choice button description string — longer text shown under the name outcomes object[] — plain-object outcome entries { id, order, type, target, config }, same shape as TriggerOutcomeModel fields but never backed by an EmbeddedDataField. Deliberately NOT nestable — a userChoice inside an option's outcomes is rejected by the config UI, not by this executor. }>Only
{ id, name, description }per option crosses into the prompt spec sent topromptFor— the nestedoutcomesarrays never leave the GM/ executor client, since they're plain data (not sanitized for socket transport) and only ever need to run locally once a choice comes back.promptForis always called —cfg.sampleCount > 0only changes which options are included in the spec'soptionsarray (the narrowed subset instead of the full pool); it never skips the call itself.On response, the single chosen option's
outcomesruns through_runOutcomeListagainst the same context (soNegateSignal,context._actorUpdateBatch, andcontext.outcomeResultsall compose with the rest of the trigger's outcome list). Stampscontext.outcomeResults.userChoice[subject.id] = { chosenOptionId, chosenOptionName }— exactly one option is ever chosen (by the human), regardless of whether the pool was narrowed bysampleCountfirst, so the result shape stays singular.