RecipeVisibilityService

Evaluates recipe visibility, knowledge access, and handles recipe learning.

Access: game.fabricate.getRecipeVisibilityService()


Methods

getVisibleRecipes(params)

Returns recipes visible to the viewer with access details.

Parameter Type Description
params.viewer User The viewing user
params.craftingSystemId string System to query
params.craftingActor Actor The crafting actor
params.componentSourceActors Actor[] Source actors

Returns: object[]. Each entry includes { recipe, visible, craftable, reason, knowledge }.

evaluateRecipeAccess(params)

Full visibility and access evaluation for a single recipe.

Parameter Type Description
params.recipe Recipe The recipe
params.viewer User The viewing user
params.craftingActor Actor The crafting actor
params.componentSourceActors Actor[] Source actors

Returns: { visible: boolean, craftable: boolean, reason: string, knowledge: object }

Possible reason values:

  • "ok" means visible and craftable.
  • "visibility" means blocked by player list.
  • "knowledge" means blocked by knowledge requirements.
  • "locked" means the recipe is locked (non-GM).
  • "missing-system" means the recipe’s system was not found.
  • "gm" means an alchemy recipe seen by a GM, who has every recipe revealed and craftable.
  • "alchemy-revealed" means an alchemy recipe revealed to the viewer in the Known list.
  • "alchemy-unrevealed" means an alchemy recipe not yet revealed to the viewer.

Alchemy mode is reveal-not-gate. For a system whose resolutionMode is "alchemy", visible reflects only whether the recipe is revealed in the player’s Known list, and craftable is always true for a non-GM regardless of reveal state. Brewing is gated solely by a matched ingredient signature, never by visibility. The system’s visibilityMode selects the reveal source: global reveals brew-discovered recipes, item reveals a linked book or scroll held on the crafting actor or a component source, knowledge reveals a learned recipe, and restricted (surfaced as “Manual” in the alchemy manager) reveals a recipe granted on the Access tab. Brew-discovery (alchemy.learnOnCraft, on by default) reveal is unioned across every mode, and learnOnCraft governs only whether a matched brew records that discovery, never whether a recipe is craftable. Because global reveals from brew-discovery alone, turning learnOnCraft off under that mode reveals nothing to any player; the system-validation report flags the pairing as an alchemyGlobalNoDiscovery warning. A time-gated brew records its discovery when the gate matures and the run is finished, not when it is started. For non-alchemy modes craftable still follows the mode’s gating rules, and guardCraftStart re-runs the same evaluation before a run starts.

evaluateKnowledgeAccess(params)

Checks whether a user has knowledge of a recipe.

Returns: { granted: boolean, reason: string, hasLearned: boolean, hasMatchedItem: boolean, matchedItems: Item[] }

guardCraftStart(params)

Guard check before starting or resuming a crafting run. For a non-GM it first runs a system-validity check on the recipe’s crafting system. When the system has a blocker that makes it unusable it returns craftable: false with reason "system-invalid", and when this specific recipe is individually hidden it returns reason "visibility". This runs even when the recipe is targeted directly, so a non-GM cannot bypass visibility by passing a recipe that never appeared in their listing. A GM bypasses this check so they can still reach a broken system to diagnose it. It then delegates to evaluateRecipeAccess, so it otherwise returns the same access object and blocks the action when craftable is false. For an alchemy system craftable is always true for a non-GM once the system-validity check passes, so this guard never blocks a valid brew on visibility.

Returns: { visible: boolean, craftable: boolean, reason: string, knowledge: object }

const vis = game.fabricate.getRecipeVisibilityService();
const guard = vis.guardCraftStart({
  viewer: game.user,
  recipe: myRecipe,
  craftingActor: actor,
  componentSourceActors: [actor]
});

if (!guard.craftable) {
  ui.notifications.warn(`Cannot craft: ${guard.reason}`);
}

learnRecipe(params)

Records a recipe as learned for the crafting actor. Optionally consumes the recipe item.

Parameter Type Description
params.recipe Recipe The recipe to learn
params.craftingActor Actor The actor who learns it
params.componentSourceActors Actor[] Source actors (for item matching)

The crafting actor (or one of the source actors) must own a matching recipe item for learning to succeed. This requirement applies to every caller, including a GM.

Returns: Promise<{ success: boolean, message: string, messageData?: object }>

message is an i18n key such as FABRICATE.Knowledge.AlreadyLearned. UI callers are expected to localize it at the presentation boundary, using messageData for interpolation when present.

expendRecipeItemUse(actor, itemId, definition)

Spends one use of a single owned recipe item copy, the same accounting the recipe-driven craft path applies when a limited-use item is consumed.

Parameter Type Description
actor Actor The actor that owns the copy
itemId string The owned copy’s document id (not a uuid)
definition object The recipe item definition the copy matches

Applies no visibility-mode or knowledge-mode gate, and no GM check of its own. The Knowledge surface’s Expend use action is the intended caller, and it applies the GM gate itself before calling this method. An already-spent copy, or a copy from a book with no use cap, performs no write at all and returns a failure result.

Returns: Promise<{ success: boolean, message: string, messageData?: object }>

cleanupLearnedRecipes(validRecipeIds)

Removes learned records for recipes that no longer exist.

Returns: Promise<void>

forgetLearnedRecipes(actor, recipeIds, options)

Deletes learned recipes from one actor. This is the shared deletion primitive behind erasing a single recipe, resetting one crafting system, and resetting every system.

Parameter Type Description
actor Actor The actor whose knowledge is cleared
recipeIds string[] Recipe ids to forget
options.freeLearnBudget boolean Free the consumed learn budget so a capped book permits re-learning (default true)
options.clearDiscovery boolean Also clear each recipe’s discovery progress (default false)

When freeLearnBudget is true, a cleared entry frees one consumed learn slot only when four things all hold: it names a source book, the actor still holds that book, the recipe still resolves, and that book actually caps learning. An entry failing any of the first three is an orphan case and gets no budget math at all: an auto-learned entry (no recorded source book), a source book the actor no longer holds, or a recipe id that no longer resolves. The fourth is not an orphan case: the source book is present and the recipe resolves, so the decrement runs, but an uncapped book’s learn count was never incremented in the first place, so the decrement has nothing to give back. See Knowledge for how the Knowledge surface states this to a GM before they erase a row.

Returns: Promise<{ success: boolean, count: number }>

forgetSystemLearnedRecipes(actor, systemId, options)

Resets one crafting system’s learned recipes for one actor, along with their discovery progress. Learned records whose recipe no longer resolves to a system are left in place.

Returns: Promise<{ success: boolean, count: number }>

forgetAllLearnedRecipes(actor, options)

Resets every learned recipe for one actor across all crafting systems, along with every discovery-progress record.

Returns: Promise<{ success: boolean, count: number }>

GM knowledge reset facade

GMs usually reach the reset through the game.fabricate.resetActorKnowledge facade rather than calling the service directly.

await game.fabricate.resetActorKnowledge({
  actorId: someActor.id,
  systemId: null, // one crafting system id, or null to reset every system
  freeLearnBudget: true
});

The facade is GM-only and takes an actor id, not an actor uuid. It never throws. It returns { success, message, messageData } so a macro can branch on the outcome and localize message at the presentation boundary. The Knowledge surface’s per-character reset control, in the Crafting Admin panel, routes through this same facade for both of its reset grains. It remains available to macros and the console for the same reset outside that UI. See Visibility & Knowledge for the GM-facing surface.


This site uses Just the Docs, a documentation theme for Jekyll.