Astral Realms Documentation Help

Pipelines

A pipeline is a small, declarative event handler attached to a blueprint. It has three stages:

Bukkit event │ ▼ ┌─────────┐ captures event state into PipelineContext │ HEAD │ (drops, XP, location, block, entity, extra context) └────┬────┘ │ ▼ ┌─────────┐ │ LINKS │ transform context state (multiply drops, take durability, │ … │ execute actions, melt, hammer, …) └────┬────┘ │ ▼ ┌─────────┐ │ TAILS │ apply side effects (drop on ground, magnet to inventory, │ … │ debug log) └─────────┘

Every component is a WrappedPipelineComponent — a typed wrapper that carries an optional requirements block. Requirements are evaluated against the player and the current PipelineContext before the component runs.

Compact component form

Any head, link or tail can be written as a bare type-id string instead of a map, for components that take no configuration:

pipelines: on-hold: head: "hold-item" # instead of: head: { type: "hold-item" } links: trace: "debug-context" # the map key stays — it is still the ordering id tails: log: "debug"

The two forms are interchangeable per component, so a pipeline can mix them: a configured head as a map next to a bare-string tail.

The compact form only works when the component needs nothing from the config — the type id resolves to a class the loader can instantiate with no arguments. Components with required fields (take-durability, execute-actions, and most other links) fail to load when written this way; give them the map form with type: and their fields.

There is also no room in the compact form for a requirements block or for a head's shared base fields (ignore-cancel, use-hook) — a component that needs either has to be a map.

How a pipeline gets wired up

  1. The blueprint is deserialised. The pipeline type strings are looked up in PipelineService's registry, which maps block-break → BlockBreakPipelineHead.class, hammer → HammerPipelineLink.class, and so on. Each component's fields are deserialised as a @ConfigSerializable record / bean.

  2. PipelineService.initialize() walks every loaded blueprint and reads the @PipelineHeadMeta annotation off the head class. The annotation says which Bukkit event and at what priority the head wants to run.

  3. EventService registers one dummy listener per (EventPriority, Event) pair. The dummy listener routes incoming events into an EventNode and from there into matching EventEntry instances:

    • preChecks — heads whose @PipelineHeadMeta(checkItem = false) runs item-agnostically (currently none registered; reserved for global hooks).

    • postChecks — heads keyed by the blueprint Key they belong to.

  4. At runtime, when Bukkit fires the event, EventProcessor.process(event, node) runs:

    • Resolve every ItemStack the event carries via the registered EventContextAdapter for that event type.

    • Pull each ItemInstance out of the PDC and look up matching postChecks entries by blueprint key.

    • The adapter builds a PipelineContext — including any extra context the event needs (for example DragAndDropContext for InventoryClickEvent).

    • Check requirements; on failure cancel the event and skip the pipeline.

    • Invoke head.handle(event, context) — if it returns false, skip the pipeline.

    • Run every chain link in order, then every tail in order.

    • If context.dirty() was set by a link, persist the updated ItemInstance back to the stack at the end of the run. If context.replaceItemStack() is also set, swap the live ItemStack for the one in context.itemStack() (used by scalable item evolution).

PipelineContext

PipelineContext is the data bus that flows through a pipeline run. Heads populate it from the event, links transform it, tails consume it.

Immutable inputs (set when the context is built):

Field

Type

Notes

player

Player

Resolved via the registered EventContextAdapter.

event

Event

The triggering Bukkit event.

itemStack

ItemStack

The custom item that triggered the pipeline.

instance

ItemInstance

UUID + blueprint + version + item data, read from the item PDC.

pipeline

Pipeline

The pipeline currently executing.

extraContext

List<Object>

Adapter-supplied extras (e.g. DragAndDropContext).

Mutable state (populated by the head, mutated by links, consumed by tails):

Field

Type

Populated by

Used by

slot

EquipmentSlot

Most heads (HAND for hold-item/unhold-item)

Listeners deciding which inventory slot to write back to

location

Location

Only block-break (the block), entity-kill (the entity), interact (the event's interaction point — null on an air click) and fish (the hook). Every other head leaves it null

drop, magnet, release-entity

block

Block

block-break, harvest-block, shear-block, interact

hammer, infinite-bucket, build-wand, plant-crops, replant-crops, spade-grass

entity

Entity

entity-damage, entity-kill, interact-entity, bucket-entity, fish, shear-entity

store-entity, release-entity

drops

List<ItemStack>

block-break, harvest-block, shear-block, shear-entity, entity-kill, fish, hammer, fell-tree, break-leaves, replant-crops

multiply-drops, melt, drop, magnet, mailbox

experience

int

block-break, entity-kill, fell-tree, replant-crops

multiply-exp, drop, magnet

dirty

boolean

Links that mutated the ItemInstance data bag (scalable, set-stored-material, …)

EventProcessor persists the instance back to the stack if true.

replaceItemStack

boolean

scalable when an item evolves to its next form

EventProcessor swaps the live stack for context.itemStack() when true.

PipelineContext doubles as a placeholder source: it implements Function<String, Object> and exposes a PlaceholderContainer pre-populated with player_*, item_* (via ItemStackPlaceholder), the instance_* placeholder, location_* if a location was captured, and an extra-context_* namespace for every adapter-supplied extra (e.g. extra-context_drag-and-drop_cursor_material). See Placeholders for the full list.

Any PlaceholderWrapper<T> in a component config — amount, multiplier, chance — resolves through this container, so you can write:

links: durability: type: take-durability amount: "%instance_data_scalable_level% / 5"

Extra context

Some events carry data that doesn't fit the standard (player, item, drops, …) model. Event context adapters can attach typed payloads to context.extraContext() that components retrieve with context.extraContext(Class):

Event

Extra context payload

Used by

InventoryClickEvent

DragAndDropContext — cursor stack + its ItemInstance (if any)

drag-and-drop head, set-stored-material, repair, unbreakable links

Any event whose head captures a Block

AgeableBlockContext — age/maximum-age/grown

Read from YAML as %extra-context_ageable_*%

AgeableBlockContext is the exception to the rule above: no event context adapter attaches it. The PipelineContext constructor adds it whenever the context has a block, the block's BlockData is Ageable, and no AgeableBlockContext is present yet.

Each payload implements ComplexPlaceholder so its fields are reachable from YAML through the extra-context_<namespace>_… chain (e.g. %extra-context_drag-and-drop_cursor_material%).

Authoring a pipeline

A pipeline is declared inside a blueprint's pipelines block. The map key is the pipeline's id (unique per blueprint), the value is the pipeline definition:

pipelines: area-mine: head: type: block-break whitelist: [STONE, COBBLESTONE, IRON_ORE] links: hammer: type: hammer radius: 1 depth: 0 durability: type: take-durability amount: 1 tails: magnet: type: magnet

The pipeline above:

  1. Captures BlockBreakEvent only for stone-family blocks (head whitelist).

  2. Adds neighbouring blocks' drops to the context via hammer.

  3. Takes 1 durability from the item via take-durability.

  4. Sends everything in drops straight to the player's inventory via magnet.

For the full catalogue of components see:

Type registry

PipelineService registers components under short string IDs. These are the strings you put in type:.

Kind

Type IDs

Heads

block-break, entity-damage, entity-kill, bucket-entity, fish, interact, interact-entity, equip-hand, hold-item, unhold-item, drag-and-drop, harvest-block, shear-block, shear-entity, equip-armor, unequip-armor

Links — generic

take-durability, repair, unbreakable, execute-actions, execute-random-actions, multiply-drops, multiply-exp, remove-item, remove-cursor, set-item-model, debug-context

Links — data

scalable, set-stored-material, store-entity, release-entity

Links — ability

melt, hammer, infinite-bucket, break-leaves, build-wand, fell-tree, firework-boost, plant-crops, replant-crops, spade-grass

Tails

drop, magnet, mailbox, debug

The registry is populated in PipelineService's constructor. Custom components are not yet pluggable from outside the plugin.

Last modified: 03 September 2026