Astral Realms Documentation Help

AstralRecipes Overview

AstralRecipes registers custom crafting, cooking, and stonecutting recipes from plain YAML files. It is a standalone Paper plugin split out of AstralEssentials' older RecipeService, and it now runs its own matching engine rather than handing recipes to Bukkit: ingredients are identified by item id (so items from CraftEngine and other ItemStackSupplier providers can be crafted), and a mirrored backing vanilla recipe is registered alongside each one purely so the recipe book, crafters, and furnaces light up.

It depends only on AstralCore (depend: [AstralCore] in plugin.yml, load: STARTUP, api-version: 26.2) and has no commands, placeholders, events, or messages of its own.

Loading lifecycle

AstralRecipes#onEnable:

  1. Registers the seven recipe readers (see Recipe types) in the static AstralRecipes.RECIPE_SERIALIZERS, a NamedRegistry<RecipeSerializer<? extends Recipe>>. The three crafting/stonecutter types are registered explicitly; the four cooking types are registered by looping over CookingType.values().

  2. Registers two Configurate type serializers on the plugin's configuration manager — RecipeTypeSerializer for Recipe (dispatches on the file's type key) and IngredientTypeSerializer for Ingredient.

  3. Constructs RecipeService and registers CraftingListener.

  4. Schedules loadConfiguration() 10 ticks later (Bukkit.getScheduler().runTaskLater(this, this::loadConfiguration, 10L)) rather than loading synchronously, so item providers such as CraftEngine have finished registering their items before ingredient ids are resolved.

loadConfiguration() calls RecipeService#load(), which:

  1. Unloads the previous pass — see Reload.

  2. Reads plugins/AstralRecipes/recipes/ through configurationManager().loadFolder("recipes", Recipe.class). The folder is created if missing, walked recursively, and every .yml/.yaml file is deserialized into a Recipe. A file that fails to parse is logged as Failed to load configuration file: <path> and skipped — the rest still load.

  3. Registers backing recipes — each recipe is mirrored as a Bukkit recipe and added with Bukkit.addRecipe(recipe, false), plus (for crafter-compatible recipes that use a custom item) a hidden material-based crafter trigger. See Backing recipes.

  4. Unlocks every backing recipe in the recipe book of everyone already online (the join handler covers players who connect later).

  5. Logs a summary line that is the quickest health check after an edit: Loaded <n> recipe(s) (<n> in recipe book, <n> crafter-compatible, <n> custom-item crafter trigger(s)).

There is no first-run seeding. The plugin jar carries three annotated example files (ruby_sword.yml, ruby_block.yml, smelt_ruby.yml) as reference, but nothing copies them to disk — recipes/ is created empty and every recipe is author-provided.

Reload

AstralRecipes has no reload command of its own. Reloading goes through AstralCore's generic /core reload AstralRecipes (permission core.commands), which calls loadConfiguration() on any AstralPaperPlugin. A recipe file added, edited, or removed on disk takes effect immediately, with no restart.

RecipeService#unload() — run at the start of every load and again on onDisable — removes every backing key and every crafter-trigger key it is tracking with Bukkit.removeRecipe(key, false) and clears both maps. The false matters: each add/removeRecipe otherwise re-finalizes the server's recipe manager and resends advancement data to every online player, which froze the main thread for tens of seconds on a large recipe set. Registration and removal are batched instead, with a single Bukkit.updateRecipes() afterwards.

Recipe types

type value

Recipe class

Station

Backing vanilla recipe

shaped

CustomShapedRecipe

Crafting table, 2×2 grid, crafter

ShapedRecipe

shapeless

CustomShapelessRecipe

Crafting table, 2×2 grid, crafter

ShapelessRecipe

stonecutter

CustomStonecutterRecipe

Stonecutter

StonecuttingRecipe

furnace

CustomCookingRecipe

Furnace

FurnaceRecipe

blasting

CustomCookingRecipe

Blast furnace

BlastingRecipe

smoking

CustomCookingRecipe

Smoker

SmokingRecipe

campfire

CustomCookingRecipe

Campfire and soul campfire

CampfireRecipe

The type key is matched case-sensitively (NamedRegistry#findByName is a plain map lookup). A missing type throws Missing recipe 'type'; an unrecognized one throws Unknown recipe type: <value>. Either way loadFolder catches it per file, logs it, and skips just that file.

See Recipe files for the field reference and YAML shape of every type.

Developer access

RecipeService is reachable off the plugin instance via the Lombok fluent accessor plugin.recipes() (this project's lombok.config sets lombok.accessors.fluent = true, so there is no getRecipes()). It is not registered with AstralPaperAPI's service registry, so a consumer must resolve the plugin instance directly:

AstralRecipes recipes = (AstralRecipes) Bukkit.getPluginManager().getPlugin("AstralRecipes"); RecipeService service = recipes.recipes();

Method

Returns

Use

load()/unload()

void

Re-read recipes/ and (un)register the backing recipes.

recipes()

Set<Recipe>

Snapshot of every loaded recipe.

findMatching(RecipeInput)

Optional<Recipe>

First recipe matching a CraftingInput (grid) or SingleItemInput.

findCooking(CookingType, RecipeInput)

Optional<Recipe>

Same, restricted to one cooking station.

byBackingKey(NamespacedKey)

Recipe (nullable)

Resolve the custom recipe behind a registered Bukkit key — covers both backing recipes and crafter triggers.

crafterCompatible(Recipe)

boolean

Whether a crafter may run it (false when any ingredient sets consume: false).

discoverAll(Player)

void

Unlock every backing recipe in a player's recipe book.

Recipes themselves implement Recipe (key(), matches(RecipeInput), takeInput(RecipeInput[, reserve]), assemble(RecipeInput), ingredients()). Custom recipe types can be added at runtime by registering a RecipeSerializer under a new name in AstralRecipes.RECIPE_SERIALIZERS before the 10-tick load fires.

Where to go next

Last modified: 03 September 2026