Skip to content

Action System

The action system describes, in YAML, what should happen after a business event. It is one of the most frequently used CoreLib features, and shows up across Forge, Strengthen, Gem, Cooking, Skills, Item, Attribute, and Level.

A pipeline is one line of text

Field names differ between modules — actions.success, actions, deny_actions, result.success.actions — but the model is the same: every line in the list is an independent pipeline, and the lines run in order.

A pipeline chains stages with |, and data flows left to right:

text
self | chance 25% | after 20t | send_message text="<green>Triggered</green>"

Read that as: take the caster as the target, pass a 25% roll, wait 20 ticks, then send the message. What travels between stages is a target flow — a list of targets. That split is the point of the model: picking targets and acting on them are different stages. The old killentity radius=5 limit=3 type=zombie bundled the search into the effect; now the search is nearby and the effect is kill_entity.

yaml
actions:
  success:
    - 'self | send_message text="<green>Forge success!</green>"'
    - 'self | play_sound sound=minecraft:entity.player.levelup volume=1 pitch=1'
    - 'run_command_as_console command="say %player_name% completed a forge"'

NOTE

Stage arguments are placeholder-resolved before execution. CoreLib supplies %player%, %player_name%, %player_uuid%, %player_world%, %player_x%, %player_y%, %player_z%, resolves whatever the calling module wrote into the context, and resolves PlaceholderAPI placeholders. Variables the pipeline sets itself read back as %var.<name>%. For module-specific variables, see that module's page.

Third-party execution and structured results

Third-party plugins can compile and execute a complete pipeline with EmakiCoreLibApi.executeActionLineAsync(plugin, line, CoreActionExecutionContext). It is callable from any thread. The future may complete on the calling thread (for example, an immediate validation result) or in the final stage's execution domain, so schedule through EmakiCoreLibApi.scheduling() before a continuation touches Bukkit. Compilation and business failures complete normally as structured results; do not rely on exceptional completion alone.

CoreActionExecutionResult contains status, reasonKey/reasonArguments, compile diagnostics, ordered stages summaries, and keptTargets. Statuses are SUCCESS, SKIPPED, PARTIAL, COMPILE_FAILED, EXECUTION_FAILED, INVALID_REQUEST, and UNAVAILABLE, allowing callers to distinguish no work, partial completion, invalid configuration, and runtime failure.

The facade also exposes read-only registry queries: actionStages() / actionStage(id) and actionTriggers() / actionTrigger(id). For a custom stage, CoreActionExecutionTarget.contextEntity() selects the entity-context execution domain; planning contexts use context.target() or context.caster() (not subject()). Revoke the returned CoreStageRegistration with registration.close().

The compatibility callback onStageRegistryRebuilt(owner, callback) replaces the previous callback for the same owner. Use addStageRegistryRebuildListener(owner, callback) when one plugin needs multiple independent callbacks; each is appended independently and returns a closeable handle.

Minimal third-party execution example:

java
CoreActionExecutionContext context = CoreActionExecutionContext.builder()
    .caster(player)
    .build();
EmakiCoreLibApi.executeActionLineAsync(this,
    "self | send_message text='<green>Hello</green>'", context)
    .thenAccept(result -> getLogger().info(result.status().name()));

Syntax

The authoritative grammar:

text
line     := node ('|' node)*
node     := branch | weighted | stage
branch   := 'if' condition '[' line ']' ('else' '[' line ']')?
weighted := 'weight' (weight '[' line ']')+
stage    := name arg*
arg      := key '=' value | bare_value

Key points:

  • A pipeline is always one line of text. There is no structured YAML form and no second parser.
  • Stage name and arguments are separated by spaces, not parentheses. Write send_message text="...", not send_message(text="...").
  • Arguments come in two forms: key=value, or a bare positional value. Bare values map onto positional parameters in declaration order, so chance 25% is the same as chance chance=25%.
  • Stage names are lower-cased, so Send_Message and send_message are equivalent. Argument names are lower-cased too.
  • Blank lines and lines starting with # are ignored.
  • Single and double quotes both work. Inside quotes, \n, \t, \\, \", and \' escape. Quoted |, [, and ] are ordinary characters, so the bar in text="a | b" is not a separator.
  • Outside quotes, |, [, and ] split tokens even without surrounding spaces: a|b and a | b are identical.
  • || is the escaped bar, which is also logical OR in conditions. It is never treated as a separator.

Three roles

Every stage has a fixed role, and that role decides where it may appear in a pipeline:

RolePurposeExamples
sourceProduces the target flow. Usually the first stage.self, looking_at, nearby radius=5
gateFilters, sorts, controls timing, or writes context values.chance 25%, where ..., after 20t
actionProduces the actual effect, applied to every target in flow.send_message, damage, give_money

The role belongs to the stage, not to the position, which is why /emakicorelib action list groups by role instead of printing one flat list: a given id is only legal in one kind of slot.

Omitting the source defaults to self. That default has a consequence worth remembering: to act on the targets your caller passed in, you must write inherited explicitly, or the pipeline silently acts on the caster.

Branches

Conditions are no longer a prefix in front of a stage. They are a node in the pipeline:

text
if %player_world%=="world" [ self | send_message text="In the overworld" ] else [ self | send_message text="Somewhere else" ]

The else branch is optional. A branch body is a complete pipeline in its own right, with its own source and as many stages as you like. Conditions support &&, ||, !, parentheses, and the comparisons <, <=, ==, !=, >=, >. Quote the right-hand side of string comparisons so they are not mistaken for arithmetic. Remember that logical OR is || inside a branch condition.

A branch body can also hold a single gate, for example stop to end early:

text
looking_at | if %var.dead% [ stop ] else [ damage amount=5 ]

Weighted branches

Where if picks one of two by condition, weight picks one of many by weight: each weight is followed by its own branch body, and at runtime exactly one body is chosen in proportion to its share of the total.

text
self | weight 70 [ send_message text="<gray>Common</gray>" ] 25 [ send_message text="<blue>Rare</blue>" ] 5 [ broadcast_message text="<gold>Epic!</gold>" ]

Read this as: roll once at relative odds 70/25/5, then run only the body that was drawn. Weights need not total 100 — writing 7 2.5 0.5 gives identical probabilities, because a weight is a relative share. See Weights.

Each weight sits directly in front of its own body rather than in one separate list. That is deliberate: had the weights been collected as [70, 25, 5] and matched positionally against the bodies that follow, inserting, deleting, or reordering a branch would silently misalign them, and that misalignment is invisible at load time — it only shows up as "the odds feel wrong".

Key points:

  • A branch body is a complete pipeline, exactly like an if body, with its own source and as many stages as you like.
  • weight selects exactly one body. If you want each outcome judged independently so several can fire at once, that is multiple chance lines, not weight.
  • A weight of 0 disables that branch; it can never be drawn. All weights being 0 is a load-time error, because the stage could then never select anything.
  • Weights may be expressions or placeholders, for example weight %var.luck% [ ... ] 30 [ ... ]. Placeholders are rendered before execution, so the odds can follow player state.
  • Weights must be non-negative. A negative weight is rejected at load time (literals) or reported at runtime (rendered placeholders) rather than ignored — otherwise one stray minus sign would make a branch vanish without a word.
  • Weights are unnamed values, never name=value. weight chance=70 [ ... ] is an error.
  • weight and if share the single nesting limit action.pipeline.max_branch_depth (default 16), and may be nested inside each other.

weight and chance answer different questions. chance 25% is a single gate: miss the roll and the whole pipeline ends. weight is a multi-way choice that always selects something (unless every weight is 0). Use chance for "fires 25% of the time, otherwise nothing happens"; use weight for "always happens, but which outcome is decided by odds".

text
self | weight 1 [ nearby radius=5 | damage amount=8 ] 1 [ send_message text="<gray>Missed this time</gray>" ]
self | if %var.is_boss% [ weight 80 [ run drop_common ] 20 [ run drop_rare ] ] else [ run drop_trash ]

Named sequences and run

A group of pipelines you reuse can be defined as a named sequence and called with run:

yaml
action:
  templates:
    reward_common:
      - 'self | send_message text="<green>Reward claimed</green>"'
      - 'self | play_sound sound=minecraft:entity.experience_orb.pickup volume=0.8 pitch=1.2'
    level_up_effect:
      - 'self | send_title title="<gold>Level up!</gold>" subtitle="<gray>Now level %var.level%</gray>"'
      - 'self | play_sound sound=minecraft:entity.player.levelup volume=1 pitch=1'
      - 'self | spawn_particle particle=totem_of_undying count=30 offset_x=0.5 offset_y=1 offset_z=0.5'

Arguments go straight after run, and the sequence body reads them as %var.<name>%:

text
run reward_common
run level_up_effect level=10
run reward_with_amount amount=100 reason=daily_login

Sequences can live under action.templates in the CoreLib main config or in a business module's own data directory. run only cares about the name, not where it was defined. Names are case-insensitive.

Sequences are compiled and cycle-checked at load time, so mutual recursion, calls to names that do not exist, and missing required arguments are reported when the config loads rather than at runtime.

Compile limits

These limits are checked at load time. Exceeding one rejects that config entry and keeps the last valid config instead of silently truncating — a truncated times=100000 looks like it worked, which is harder to diagnose than an error.

Config keyDefaultMeaning
action.pipeline.max_repeat_times100Upper bound for every <interval> times <n>
action.pipeline.max_sequence_depth8Maximum run nesting depth
action.pipeline.max_branch_depth16Maximum if and weight nesting depth

Loop task limits

Long-running loops started by start_task are governed separately by action.loop.*, independently of the compile limits above.

Config keyDefaultMeaning
action.loop.enabledtrueWhether loop tasks may start at all.
action.loop.min_sync_interval5tMinimum interval for sync-domain loops.
action.loop.min_async_interval100msMinimum interval for async-domain loops.
action.loop.max_times7200Maximum executions for a single loop task.
action.loop.max_active_loops_total5000Server-wide cap on active loop tasks.
action.loop.max_active_loops_per_player16Per-player cap on active loop tasks.
action.loop.max_active_loops_per_plugin1000Per-plugin cap on active loop tasks.
action.loop.cancel_player_loops_on_quittrueCancel a player's loop tasks when they quit.
action.loop.cancel_plugin_loops_on_disabletrueCancel a plugin's loop tasks when it is disabled.

Parameter types

Stage parameters carry the types listed below, and the parameter tables in this page name them. Types are validated at load time, so a malformed value does not wait until runtime to surface.

TypeMeaningExample
STRINGText. Quote it when it contains spaces or special characters.text="<green>Success"
INTEGERWhole number.amount=3, count=10
DOUBLEDecimal number.volume=0.8, amount=25.5
BOOLEANtrue or false.delete_item=false, icon=true
DURATIONTime. Accepts t (ticks), s, ms; a bare number means ticks.20t, 1s, 500ms
TIMEThe older tick/second notation, kept for compatibility.20t, 1s
PERCENTAGEProbability. Accepts percentages, decimals, and fractions.50%, 0.5, 1/3
ENTITY_TYPEA Bukkit EntityType name.type=zombie
MATERIALA Bukkit Material name.material=stone
SOUNDA sound key: a Bukkit Sound enum name or namespace:key.sound=minecraft:block.anvil.use
EXPRESSIONArithmetic evaluated by the CoreLib expression engine.amount=%var.level%*4+18

Migrating from the old syntax

If you still have old config, three rules cover most of the rewrite:

  • Stage ids are underscore-separated. sendmessage becomes send_message, givemoney becomes give_money, runcommandasconsole becomes run_command_as_console, and so on for every id.
  • @ control prefixes became stages in the pipeline. @chance=25% becomes chance 25%, @delay=20t becomes after 20t, and @if=<condition> becomes an if <condition> [ ... ] branch. @ignore_failure has no replacement: failure semantics now belong to each stage, so where you need "failure must not block", use a branch or move that stage onto its own line.
  • Write the source. Old action lines implicitly acted on the player from the event. A pipeline states its source: self for the caster (also the default when omitted), inherited for the targets your caller passed in.

A few ids changed by more than an underscore:

OldNow
raysource looking_at
loopsync / loopasyncstart_task (sync/async is no longer a config option; see that stage)
cancelloopstop_task
@template=name / usetemplaterun name
killentity radius=... type=...source nearby does the search, kill_entity only removes
createitem id=... senditem id=...create_item publishes the single pipeline item, send_item reads it; no id
castmythicskillcast_mythic_skill, provided by EmakiSkills

cast_skill carries one breaking semantic change: its skill argument is now an EmakiSkills skill id, no longer a MythicMobs skill id. Old config written as cast_skill skill=<mythic skill> must become cast_mythic_skill skill=<mythic skill>, otherwise it fails looking for an EmakiSkills skill that does not exist.

Built-in sources

A source produces the target flow and usually opens the pipeline. Omitting the source is equivalent to self.

self

The caster. No parameters.

text
self | send_message text="<green>Done</green>"

inherited

The target flow handed in by the caller or by the previous phase. No parameters.

You have to write this one out: omitting the source falls back to self, so a pipeline that meant to act on inherited targets silently acts on the caster instead.

Pairs with keep: an earlier phase writes looking_at | keep to record the flow, and a later phase reads it back with inherited.

text
inherited | damage amount=8

trigger

The entity a trigger named, for triggers whose subject differs from the caster. No parameters.

The context holds a name or UUID rather than an entity reference. Pipelines may be compiled long before they run, and parking live entities in context keys gets in the way of collection.

text
trigger | send_message text="<yellow>You set off the trap</yellow>"

origin

The pipeline's spatial reference point, as a location target. No parameters.

text
origin | spawn_particle particle=flame count=20

looking_at

The entity under the caster's crosshair.

ParameterTypeRequiredDefaultDescription
rangeDOUBLENo5Ray length.
widthDOUBLENo0.5Ray width.
text
looking_at range=12 | damage amount=6
looking_at | keep | send_message text="<red>You are locked on</red>"

nearby

Entities around the pipeline reference point.

ParameterTypeRequiredDefaultDescription
radiusDOUBLENo1Search radius.
limitINTEGERNo1Maximum entities returned.
typeENTITY_TYPENo""Entity type filter; empty means no filter.
include_playersBOOLEANNofalseWhether players count as candidates.

Results are sorted by distance from the reference point, then truncated to limit. Finding no entity is "empty" and skips the following stages; naming an entity type that does not exist is "invalid" and raises an error. The old implementation treated a typo'd type as a skip, which meant server owners never saw their own mistake.

text
nearby radius=8 limit=5 type=zombie | damage amount=10
nearby radius=4 limit=20 include_players=true | give_potion_effect type=slowness level=1 duration=5s

nearby_players

Players around the pipeline reference point.

ParameterTypeRequiredDefaultDescription
radiusDOUBLENo1Search radius.
limitINTEGERNo0Maximum returned; 0 means all.

Same filtering logic as nearby, restricted to players. limit defaults to 0 (all) rather than 1, because party-wide and area-wide effects normally want every player, not the closest one.

text
nearby_players radius=10 | send_message text="<gold>Your party gained a buff</gold>"

offset

A location offset from the pipeline reference point.

ParameterTypeRequiredDefaultDescription
xDOUBLENo0X offset.
yDOUBLENo0Y offset.
zDOUBLENo0Z offset.
relativeBOOLEANNofalseOffset along the reference point's own facing.

With relative=false the offset follows world axes. With relative=true it follows the reference point's facing, where z is forward and x is right.

text
offset y=2 | spawn_particle particle=end_rod count=30
offset z=3 relative=true | explosion power=2 break_blocks=false

at

An absolute coordinate, or one relative to the reference point.

ParameterTypeRequiredDefaultDescription
worldSTRINGNo""World name; empty uses the reference point's world.
xSTRINGNo~X coordinate, supports ~.
ySTRINGNo~Y coordinate, supports ~.
zSTRINGNo~Z coordinate, supports ~.

The coordinates are STRING rather than DOUBLE so that ~ notation keeps working: ~ is "the reference point's value on this axis" and ~5 is "five more than that", matching vanilla command coordinate syntax.

text
at world=world x=100 y=65 z=-30 | spawn_particle particle=end_rod count=50
at x=~ y=~-1 z=~ | set_block material=oak_planks

player_by_name

An online player identified by name or UUID.

ParameterTypeRequiredDefaultDescription
nameSTRINGYesPlayer name or UUID.

name is positional, so name= can be dropped.

text
player_by_name Notch | send_message text="<yellow>You were called out</yellow>"
player_by_name %var.winner% | give_money amount=500

Built-in gates

A gate filters the target flow, controls timing, or writes values into the pipeline context. Gates produce no effects of their own.

where

Empties the target flow when the condition is false.

ParameterTypeRequiredDefaultDescription
conditionSTRINGYesBoolean condition.

condition is positional. Placeholders are already substituted before the gate runs, so expressions like %target.health%<10 work directly.

The current granularity is the whole flow, not per target: a gate is invoked once per flow, with arguments rendered against the first target, so where either keeps the entire flow or clears it. For single-target flows — the common looking_at | where ... | damage shape — the two semantics are indistinguishable.

text
looking_at | where %target.health%<10 | kill_entity
nearby radius=6 limit=10 | where %player_world%=="world" | damage amount=4

chance

Ends the pipeline when the roll fails.

ParameterTypeRequiredDefaultDescription
chancePERCENTAGEYesProbability, such as 50%, 0.5, 1/3.

chance is positional. A failed roll reports "stopped", not "invalid" — losing the roll is the whole point of the stage. chance abc, on the other hand, is a typo the server owner needs to see as an error.

When something should always happen but which outcome is decided by odds, use weighted branches instead of stacking chance lines — separate lines are judged independently, so none may fire, or several may fire at once.

text
self | chance 10% | broadcast_message text="<gold>%player_name% hit a rare reward!</gold>"
self | chance 1/3 | give_money amount=100 provider=vault

limit

Keeps the first count targets in the flow, preserving order.

ParameterTypeRequiredDefaultDescription
countINTEGERYesHow many targets to keep.

count is positional.

text
nearby radius=15 limit=50 | sort_by distance | limit 3 | damage amount=12

sort_by

Sorts the target flow by distance or health.

ParameterTypeRequiredDefaultDescription
keySTRINGYesdistance or health.
orderSTRINGNoascasc or desc.

key is positional. Combined with limit this expresses "the three closest" or "the one with the least health".

text
nearby radius=20 limit=30 | sort_by health order=asc | limit 1 | kill_entity
nearby_players radius=25 | sort_by distance order=desc | limit 5 | send_title title="<red>Too far from the fight</red>"

set

Writes pipeline variables, read back as %var.<name>%.

This stage declares no parameters, because the writer of the pipeline chooses the names — CoreLib cannot know in advance about a variable named by set damage=%var.level%*4+18. Load-time validation skips the unknown-parameter check for this stage.

Values that evaluate as arithmetic are stored as numbers, so %var.damage% reads back as 22 rather than %var.level%*4+18. Everything else is stored verbatim.

text
self | set damage=%var.level%*4+18 | set label=Critical | send_message text="<red>%var.label% %var.damage%</red>"

keep

Marks the current target flow as the one to carry into the next phase. No parameters.

The flow already passes between stages inside a pipeline, so this gate passes it through untouched. What makes it more than a no-op is that the interpreter records the flow it sees, and that recording is how one phase hands its targets to the next. A skill script writes looking_at | keep in its cast phase and reads it back with inherited in its hit phase.

It records the flow as of the moment it runs, not the pipeline's final flow, so where the line sits within the phase does not change the result: later gates narrowing the flow do not alter what was recorded, and a second keep overwrites the first.

text
looking_at | keep | send_message text="<gray>Target locked</gray>"

stop

Ends the pipeline here. No parameters.

It reports "stopped", so the pipeline result is a skip rather than a failure — deliberately stopping is not an error. Mostly used inside branches.

text
looking_at | if %var.dead% [ stop ] else [ damage amount=5 ]

create_item

Builds an item and publishes it as the pipeline's item value.

ParameterTypeRequiredDefaultDescription
item_sourceSTRINGNo""Item source.
amountINTEGERNo1Item amount.

It is registered as a gate rather than an action because writing typed context is a gate's job: only a gate's passed result is written back into the pipeline context. That is also why it passes the target flow through unchanged — the stage adds a value, it does not consume targets.

The pipeline holds a single item key, so there is nothing to name and the old id parameter is gone. A second create_item in the same pipeline replaces the first one's value.

item_source uses the CoreLib hyphenated item source shorthand — see Item Sources — for example vanilla minecraft-diamond or custom emakiitem-flame_blade. Vanilla sources do not accept colon notation such as minecraft:diamond.

text
self | create_item item_source=minecraft-golden_apple amount=2 | send_item
self | create_item item_source=emakiitem-flame_sword | send_item

after

Delays every stage after it.

ParameterTypeRequiredDefaultDescription
delayDURATIONYesDelay, such as 10t, 500ms, 2s.

delay is positional. This is a timing stage rather than a flow transform: the interpreter recognizes after, treats the rest of the pipeline as its body, and schedules that body with the delay. The body re-validates caster, targets, and owning plugin before it runs, so a target that disappeared during the wait produces a skip rather than an error.

text
self | after 1s | send_message text="<gray>Sent one second later"
self | send_message text="<gold>Ready</gold>" | after 20t | send_message text="<red>Go!</red>"

every

Repeats every stage after it on an interval.

ParameterTypeRequiredDefaultDescription
intervalDURATIONNo1tInterval, such as 20t or 1s.
timesINTEGERNo0Extra runs after the first.

Written as every <interval> times <n>. times counts runs beyond the first, so times 0 (the default) means the body runs once. The count is bounded by action.pipeline.max_repeat_times (default 100), and exceeding it rejects the config entry.

text
self | every 10t times 5 | spawn_particle particle=flame count=10
origin | every 1s times 3 | play_sound sound=minecraft:block.note_block.bell volume=1 pitch=1.5

every suits short bursts inside one pipeline. For a long-running loop you can cancel by key, use start_task.

Built-in actions

Actions are the stages that produce effects. The "target requirement" noted for each stage says what kind of target flow it needs; when the requirement is not met the stage is skipped rather than failing the whole pipeline.

  • NONE: needs no target at all.
  • OPTIONAL: runs either way, and still runs once with zero targets.
  • REQUIRED_ENTITY: needs at least one entity target.
  • REQUIRED_LOCATION: needs at least one location target.
  • REQUIRED_ANY: needs at least one target, entity or location.

Messages and feedback

send_message

Sends a MiniMessage chat message. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
textSTRINGYesMessage text in MiniMessage.
yaml
actions:
  # Plain message
  - 'self | send_message text="<green>Done!</green>"'
  # With a placeholder
  - 'self | send_message text="<yellow>Welcome back, %player_name%!</yellow>"'
  # Richer MiniMessage: gradient plus hover and click
  - 'self | send_message text="<hover:show_text:''Click for details''><click:run_command:/menu><gradient:gold:yellow>Open menu</gradient></click></hover>"'

send_action_bar

Sends an action bar message. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
textSTRINGYesAction bar text in MiniMessage.
yaml
actions:
  - 'self | send_action_bar text="<green>+50 XP</green>"'
  - 'self | send_action_bar text="<gray>Cooking: <green>████</green><dark_gray>██████</dark_gray> 40%</gray>"'

send_title

Shows a title and subtitle. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
titleSTRINGYesMain title in MiniMessage.
subtitleSTRINGNo""Subtitle in MiniMessage.
fade_inDURATIONNo10tFade-in time.
stayDURATIONNo40tHold time.
fade_outDURATIONNo10tFade-out time.
yaml
actions:
  # Plain title
  - 'self | send_title title="<gold>Quest complete</gold>" subtitle="<gray>Experience awarded</gray>"'
  # Custom timings
  - 'self | send_title title="<red>Warning</red>" subtitle="<yellow>Entering a danger zone</yellow>" fade_in=5t stay=60t fade_out=20t'
  # Title only, quick flash
  - 'self | send_title title="<bold><gold>LEVEL UP!</gold></bold>" fade_in=0t stay=20t fade_out=5t'

broadcast_message

Broadcasts a MiniMessage message to the server. Target requirement NONE.

ParameterTypeRequiredDefaultDescription
textSTRINGYesBroadcast text in MiniMessage.

No target is needed, so the source can be left out:

yaml
actions:
  # Server announcement
  - 'broadcast_message text="<gold>[Notice] <white>Restarting in 5 minutes</white></gold>"'
  # Achievement broadcast
  - 'broadcast_message text="<light_purple>✦ %player_name% completed a legendary forge! ✦</light_purple>"'

play_sound

Plays a sound for the target. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
soundSOUNDYesSound key: a Bukkit Sound enum name or a minecraft: style key.
volumeDOUBLENo1Volume; above 1 increases audible range.
pitchDOUBLENo1Pitch, 0.52.0, where 1 is normal.
yaml
actions:
  # Level-up chime
  - 'self | play_sound sound=minecraft:entity.player.levelup volume=1 pitch=1'
  # Anvil, lower pitch and audible from further away
  - 'self | play_sound sound=minecraft:block.anvil.use volume=2 pitch=0.8'
  # Play for everyone nearby
  - 'nearby_players radius=12 | play_sound sound=minecraft:entity.villager.no volume=1 pitch=1'

spawn_particle

Spawns particles at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
particleSTRINGYesParticle key (Bukkit Particle enum name).
countINTEGERNo1Particle count.
offset_xDOUBLENo0Spread along X.
offset_yDOUBLENo0Spread along Y.
offset_zDOUBLENo0Spread along Z.
extraDOUBLENo0Extra data (speed or data, depending on particle type).

Coordinates are no longer this stage's parameters: the source decides the location. self is the player's position, and at, offset, origin give an explicit one.

yaml
actions:
  # Green particles on the player for success feedback
  - 'self | spawn_particle particle=happy_villager count=15 offset_x=0.3 offset_y=0.5 offset_z=0.3'
  # Flames on the player for a skill cast
  - 'self | spawn_particle particle=flame count=30 offset_x=0.5 offset_y=0.2 offset_z=0.5 extra=0.05'
  # Fixed world coordinates
  - 'at world=world x=100 y=65 z=-30 | spawn_particle particle=end_rod count=50 offset_x=1 offset_y=2 offset_z=1 extra=0.02'
  # Two blocks above the player
  - 'offset y=2 | spawn_particle particle=explosion count=3 offset_x=0.1 offset_y=0.1 offset_z=0.1'

boss_bar_show

Shows a boss bar to the target. Target requirement REQUIRED_ENTITY. Calling it again with the same id updates the existing bar.

ParameterTypeRequiredDefaultDescription
idSTRINGYesBoss bar id, used to update or hide it later.
titleSTRINGYesBar title in MiniMessage.
progressDOUBLENo1Progress, from 0 to 1.
colorSTRINGNopurpleBar color.
styleSTRINGNosolidBar style.
flagsSTRINGNo""Comma-separated bar flags.
yaml
actions:
  - 'self | boss_bar_show id=forge_progress title="<gold>Forging</gold>" progress=0.5 color=yellow'
  - 'self | boss_bar_show id=forge_progress title="<green>Almost done</green>" progress=0.9'

boss_bar_hide

Hides a boss bar shown earlier. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
idSTRINGYesBoss bar id, or all to hide every bar.
yaml
actions:
  - 'self | boss_bar_hide id=forge_progress'
  - 'self | boss_bar_hide id=all'

Health and state

heal

Restores the target's health. Target requirement REQUIRED_ENTITY. Never exceeds max health.

IMPORTANT

This stage sets health directly and does not fire Bukkit's EntityRegainHealthEvent. No healing bonus or reduction applies.

ParameterTypeRequiredDefaultDescription
amountDOUBLEYesHealth restored, in half-hearts (1 = half a heart).
yaml
actions:
  # Restore 4 points (two hearts)
  - 'self | heal amount=4'
  # Full heal; the excess is clamped to max health
  - 'self | heal amount=9999'
  # Small heal behind a roll
  - 'self | chance 30% | heal amount=2'

damage

Removes health from the target. Target requirement REQUIRED_ENTITY. Health floors at 0.

IMPORTANT

This stage sets health directly and does not fire Bukkit's EntityDamageEvent. Armor, resistance, and enchantments do not reduce it, and it cannot kill a player because the floor is 0.

ParameterTypeRequiredDefaultDescription
amountDOUBLEYesDamage amount.
yaml
actions:
  # Remove 2 health (one heart)
  - 'self | damage amount=2'
  # Damage whatever is under the crosshair
  - 'looking_at range=10 | damage amount=8'
  # Behind a condition
  - 'if %player_world%=="world_nether" [ self | damage amount=1 ]'

set_health

Sets the target's health to a value. Target requirement REQUIRED_ENTITY. Clamped between 0 and max health.

ParameterTypeRequiredDefaultDescription
amountDOUBLEYesTarget health.

Use this stage with 0 to kill a player; kill_entity rejects player targets.

yaml
actions:
  - 'self | set_health amount=20'
  - 'self | set_health amount=1'
  - 'looking_at | set_health amount=0'

feed

Restores the target's food level. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
amountINTEGERNo20Food points restored.
saturationDOUBLENo0Saturation restored.
yaml
actions:
  - 'self | feed'
  - 'self | feed amount=6 saturation=3'

ignite

Sets the target on fire. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
durationDURATIONNo5sBurn duration.
yaml
actions:
  - 'looking_at | ignite duration=8s'
  - 'nearby radius=5 limit=10 | ignite'

extinguish

Puts out the fire on the target. Target requirement REQUIRED_ENTITY. No parameters.

yaml
actions:
  - 'self | extinguish'

kill_entity

Removes the target entity. Target requirement REQUIRED_ENTITY. No parameters.

This is what remains of the old killentity once the search was taken out: radius, limit, type, and include_players all moved to the nearby source. Choosing which entities to act on was never this stage's job; now it simply removes whatever the flow hands it.

Player targets are rejected. Calling Entity#remove on a player is not a supported operation — to kill a player, use set_health amount=0.

yaml
actions:
  # Clear up to five zombies within eight blocks
  - 'nearby radius=8 limit=5 type=zombie | kill_entity'
  # Clear whatever is under the crosshair
  - 'looking_at range=20 | kill_entity'

projectile

Launches a self-driven projectile from the caster. Target requirement OPTIONAL.

ParameterTypeRequiredDefaultDescription
speedDOUBLENo1.5Blocks travelled per tick.
gravityDOUBLENo0.05Downward pull per tick.
lifetimeINTEGERNo60Maximum lifetime in ticks.
hit_radiusDOUBLENo0.5Hit detection radius.
pierceINTEGERNo0How many extra entities it passes through.
homingBOOLEANNofalseSteer toward the current target.
homing_strengthDOUBLENo0.1Homing turn strength.
particleSTRINGNoflameTrail particle key.
damageDOUBLENo0Damage dealt on hit; 0 means none.
directionSTRINGNolookInitial direction: look (line of sight) or target.
yaml
actions:
  # Straight fireball
  - 'self | projectile speed=2 damage=8 particle=flame'
  # Homing shot, which needs a target first
  - 'looking_at range=25 | projectile homing=true homing_strength=0.2 damage=12 direction=target'
  # Piercing shot
  - 'self | projectile speed=2.5 pierce=3 damage=5 gravity=0'

Potion effects

give_potion_effect

Adds a potion effect to the target. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
typeSTRINGYesEffect type, written as speed, minecraft:strength, and so on.
levelINTEGERYesEffect level, one-based. 1 maps to Bukkit amplifier 0 (in-game I).
durationDURATIONYesDuration, such as 100t, 5s, 10000ms.
ambientBOOLEANNofalseAmbient effect (sparser, more transparent particles).
particlesBOOLEANNotrueShow particles.
iconBOOLEANNotrueShow the effect icon in the HUD.
yaml
actions:
  # Speed II for 30 seconds
  - 'self | give_potion_effect type=speed level=2 duration=30s particles=true icon=true'
  # Strength I for a minute, no particles for a subtler look
  - 'self | give_potion_effect type=strength level=1 duration=60s particles=false icon=true'
  # Night vision as an ambient effect
  - 'self | give_potion_effect type=night_vision level=1 duration=5s ambient=true'
  # Namespaced form
  - 'self | give_potion_effect type=minecraft:regeneration level=3 duration=10s'
  # Slow everything nearby
  - 'nearby radius=6 limit=10 | give_potion_effect type=slowness level=2 duration=5s'

remove_potion_effect

Removes one potion effect from the target. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
typeSTRINGYesEffect type.
yaml
actions:
  - 'self | remove_potion_effect type=slowness'
  - 'self | remove_potion_effect type=minecraft:weakness'

clear_potion_effects

Clears every active potion effect on the target. Target requirement REQUIRED_ENTITY. No parameters.

yaml
actions:
  # Cleanse
  - 'self | clear_potion_effects'
  # Cleanse two seconds later
  - 'self | after 2s | clear_potion_effects'
  # Only in a specific world
  - 'if %player_world%=="world_arena" [ self | clear_potion_effects ]'

Items

Several item stages share the same item_source parameter, whose value is the CoreLib hyphenated item source shorthand. See Item Sources.

The slot parameter accepts:

  • mainhand, main_hand, hand
  • offhand, off_hand
  • helmet, chestplate/chest, leggings/legs, boots
  • inventory indexes 0 to 35, also writable as slot_0 or hotbar_0

send_item

Hands the pipeline item to the target. Target requirement REQUIRED_ENTITY. No parameters.

This stage declares that it needs the pipeline item, which makes a send_item without a preceding create_item a load-time error rather than a runtime null: the validator compares that declaration against the context the trigger phase promises to provide.

The old id and keep parameters are both gone. The pipeline holds a single item key rather than a table indexed by name, and reading a context value does not consume it, so there is no longer anything to "keep in temporary storage".

yaml
actions:
  - 'self | create_item item_source=minecraft-golden_apple amount=2 | send_item'

give_item

Gives the item for an item source into the target's inventory. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
item_sourceSTRINGNo""Item source.
amountINTEGERNo1Amount.

The difference from create_item plus send_item: this stage gives straight from the source without going through the pipeline item, so it needs only one stage, but it also gives you no chance to do anything to the item first.

yaml
actions:
  - 'self | give_item item_source=minecraft-diamond amount=5'
  - 'nearby_players radius=10 | give_item item_source=emakiitem-strengthen_stone amount=1'

set_item

Sets the item in one of the target's slots from an item source. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
slotSTRINGNomainhandInventory slot.
item_sourceSTRINGNo""Item source.
amountINTEGERNo1Amount.
yaml
actions:
  - 'self | set_item slot=mainhand item_source=emakiitem-flame_sword'
  - 'self | set_item slot=helmet item_source=minecraft-diamond_helmet'

clear_item

Clears the item in one of the target's slots. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
slotSTRINGYesInventory slot.
item_sourceSTRINGNo""Expected item source. When set, the slot is only cleared on a match, otherwise skipped.
yaml
actions:
  # Clear the main hand unconditionally
  - 'self | clear_item slot=mainhand'
  # Only clear it if it holds a stick
  - 'self | clear_item slot=mainhand item_source=minecraft-stick'
  # Clear the helmet slot only for a matching custom item
  - 'self | clear_item slot=helmet item_source=emakiitem-cursed_helmet'
  # Clear a specific inventory index
  - 'self | clear_item slot=0'

take_item

Removes matching items from the target's inventory. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
item_sourceSTRINGNo""Item source to remove.
amountINTEGERNo1Amount to remove.
yaml
actions:
  - 'self | take_item item_source=minecraft-emerald amount=3'
  - 'self | take_item item_source=emakiitem-forge_token amount=1'

drop_item

Drops an item at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
item_sourceSTRINGNo""Item source.
amountINTEGERNo1Item amount.

The coordinates come from the source and are no longer this stage's parameters.

yaml
actions:
  # Drop above the player
  - 'offset y=2 | drop_item item_source=minecraft-diamond amount=1'
  # Drop a reward at fixed coordinates
  - 'at world=world x=100 y=65 z=-50 | drop_item item_source=minecraft-gold_ingot amount=5'
  # Drop three blocks in front of the player
  - 'offset z=3 relative=true | drop_item item_source=minecraft-emerald amount=2'

repair_item

Repairs the durability of the item in a slot. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
slotSTRINGNomainhandInventory slot.
amountINTEGERNo0Durability points repaired; 0 or less repairs fully.
yaml
actions:
  - 'self | repair_item'
  - 'self | repair_item slot=chestplate amount=50'

damage_item

Adds durability damage to the item in a slot. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
slotSTRINGNomainhandInventory slot.
amountINTEGERNo1Durability damage added.
delete_itemBOOLEANNofalseRemove the item when durability runs out.
yaml
actions:
  - 'self | damage_item amount=10'
  - 'self | damage_item slot=mainhand amount=100 delete_item=true'

Blocks and world

place_block

Places the block for an item source at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
item_sourceSTRINGNo""Block item source.

Vanilla blocks work, as do CraftEngine, ItemsAdder, Nexo, and Oraxen custom blocks. If the source is not a placeable block, the stage skips rather than breaking the pipeline. With a player context it fires the Bukkit place event, so a protection plugin cancelling it means nothing is placed.

yaml
actions:
  # Vanilla block; note the hyphen between namespace and id
  - 'at world=world x=100 y=64 z=-20 | place_block item_source=minecraft-stone'
  # Under the player's feet
  - 'at x=~ y=~-1 z=~ | place_block item_source=minecraft-oak_planks'
  # CraftEngine custom block
  - 'at x=~1 y=~ z=~ | place_block item_source=ce-cutting_board'
  # ItemsAdder custom block
  - 'offset y=2 | place_block item_source=itemsadder-decorative_lamp'

set_block

Sets the block at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
materialSTRINGNo""Block material name.
block_dataSTRINGNo""Bukkit BlockData string.
apply_physicsBOOLEANNotrueTrigger a block physics update.

The difference from place_block: this stage takes a material directly instead of an item source and does not fire the Bukkit place event, so it can set block states that have no corresponding item.

yaml
actions:
  - 'at world=world x=50 y=70 z=100 | set_block material=chest'
  - 'at x=~ y=~-1 z=~ | set_block material=oak_stairs block_data="[facing=north,half=top]"'
  - 'origin | set_block material=air apply_physics=false'

break_block

Breaks or clears the block at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
drop_itemsBOOLEANNofalseDrop the block's items.
apply_physicsBOOLEANNotrueTrigger a physics update when clearing the block.
yaml
actions:
  - 'at x=~ y=~-1 z=~ | break_block drop_items=true'
  - 'at world=world x=10 y=64 z=10 | break_block'

explosion

Creates an explosion at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
powerDOUBLENo0Explosion power.
fireBOOLEANNofalseSet fire.
break_blocksBOOLEANNofalseBreak blocks.

The defaults — power 0, no fire, no block damage — give you sound and visuals only. Real destruction has to be turned on explicitly.

yaml
actions:
  # Cosmetic explosion
  - 'self | explosion power=2'
  # Explosion that actually breaks blocks
  - 'looking_at range=15 | explosion power=4 fire=true break_blocks=true'

spawn_entity

Spawns entities at the target location. Target requirement REQUIRED_ANY.

ParameterTypeRequiredDefaultDescription
typeENTITY_TYPEYesEntity type.
countINTEGERNo1How many to spawn.
yaml
actions:
  - 'at world=world x=100 y=65 z=-30 | spawn_entity type=zombie count=3'
  - 'offset z=4 relative=true | spawn_entity type=armor_stand'

Teleport

teleport

Teleports the target to a coordinate. Target requirement REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
worldSTRINGNo""Destination world; empty uses the target's current world.
xSTRINGNo~X coordinate. Supports ~ relative notation (~5 means X+5).
ySTRINGNo~Y coordinate. Supports ~.
zSTRINGNo~Z coordinate. Supports ~.
yawDOUBLENotarget's currentYaw (horizontal facing, 0–360). Omitted keeps the current facing.
pitchDOUBLENotarget's currentPitch (vertical facing, -90–90). Omitted keeps the current facing.

The coordinates are this stage's own parameters rather than something the source provides: the source decides who gets teleported, the coordinates decide where to, and both are necessary.

yaml
actions:
  # Fixed coordinates
  - 'self | teleport world=world x=0 y=100 z=0 yaw=180 pitch=0'
  # Relative, ten blocks up
  - 'self | teleport y=~10'
  # Across worlds
  - 'self | teleport world=world_nether x=50 y=64 z=-100 yaw=90 pitch=0'
  # Pull every nearby player to spawn
  - 'nearby_players radius=30 | teleport world=world x=0 y=65 z=0'

Economy

The three economy stages share one parameter set.

ParameterTypeRequiredDefaultDescription
amountDOUBLEYesAmount.
providerSTRINGNoautoEconomy provider: auto, vault, or excellenteconomy.
currencySTRINGNo""Currency id. Required when ExcellentEconomy runs multiple currencies.

give_money

Adds to the target's balance. Target requirement REQUIRED_ENTITY.

yaml
actions:
  # Basic, letting CoreLib pick the economy plugin
  - 'self | give_money amount=100'
  # Explicitly Vault
  - 'self | give_money amount=500 provider=vault'
  # ExcellentEconomy with multiple currencies
  - 'self | give_money amount=50 provider=excellenteconomy currency=gems'

take_money

Takes from the target's balance. Target requirement REQUIRED_ENTITY. The stage fails when the balance is insufficient.

yaml
actions:
  - 'self | take_money amount=25.5 provider=vault'
  - 'self | take_money amount=10 provider=excellenteconomy currency=coins'

set_money

Sets the target's balance to a value. Target requirement REQUIRED_ENTITY.

yaml
actions:
  - 'self | set_money amount=0 provider=vault'
  - 'self | set_money amount=1000 provider=auto'

Experience

The three experience stages share one parameter set, and all require REQUIRED_ENTITY.

ParameterTypeRequiredDefaultDescription
amountINTEGERYesExperience points or levels.
modeSTRINGNopointsMode: points (experience points) or levels.

give_exp

Adds experience to the target.

yaml
actions:
  # 100 experience points
  - 'self | give_exp amount=100 mode=points'
  # Three levels
  - 'self | give_exp amount=3 mode=levels'
  # points is the default and can be omitted
  - 'self | give_exp amount=50'

take_exp

Removes experience from the target, flooring at 0.

yaml
actions:
  - 'self | take_exp amount=200 mode=points'
  - 'self | take_exp amount=1 mode=levels'

set_exp

Sets the target's total experience or level.

yaml
actions:
  - 'self | set_exp amount=30 mode=levels'
  - 'self | set_exp amount=0 mode=points'

Commands

The three command stages share one parameter. A leading / is stripped automatically, but leaving it out of config reads better.

ParameterTypeRequiredDefaultDescription
commandSTRINGYesThe command to run, without a leading /.

run_command_as_player

Runs a command as the target player. Target requirement REQUIRED_ENTITY. Subject to the player's own permissions.

yaml
actions:
  - 'self | run_command_as_player command="spawn"'
  - 'self | run_command_as_player command="menu open main"'

run_command_as_op

Temporarily grants the target player OP, runs the command, then restores the original OP state. Target requirement REQUIRED_ENTITY.

CAUTION

This stage grants OP temporarily, which carries real risk: for the duration of the command the player holds full OP. If any part of the command comes from a placeholder the player can influence, you have handed out a privilege escalation path. Prefer run_command_as_console, or grant a temporary permission through your permissions plugin.

yaml
actions:
  - 'self | run_command_as_op command="lp user %player_name% permission set example.vip true"'

run_command_as_console

Runs a command as the console. Target requirement NONE (placeholders are still resolved).

No target is needed, so the source can be left out:

yaml
actions:
  # Server announcement
  - 'run_command_as_console command="say %player_name% completed a forge"'
  # Grant a permission
  - 'run_command_as_console command="lp user %player_name% permission set forge.master true"'
  # Call another plugin
  - 'run_command_as_console command="crate give %player_name% legendary 1"'

Long-running tasks

every covers short bursts inside a single pipeline. For a loop that runs for a long time, can be cancelled by key, and stops on its own when the player logs off or dies, use the two stages below.

start_task

Starts a named sequence that repeats on an interval. Target requirement OPTIONAL.

ParameterTypeRequiredDefaultDescription
sequenceSTRINGYesName of the sequence to repeat.
timesINTEGERNo1How many times to run.
intervalDURATIONNo20tInterval between runs.
initial_delayDURATIONNo0tDelay before the first run.
keySTRINGNo""Task key, used to cancel it later.
on_conflictSTRINGNoreplaceKey conflict policy: replace, ignore, allow_duplicate.
stop_when_offlineBOOLEANNotrueStop when the player logs off.
stop_when_deadBOOLEANNofalseStop when the player dies.
stop_whenSTRINGNo""Stop once this condition holds.
stop_on_failureBOOLEANNofalseStop when the sequence fails.

This one stage replaces the old loopsync and loopasync. In the old implementation the async marker only picked a different minimum interval and ran one extra pre-check; the scheduling was identical either way. Real thread affinity now comes from the execution domain each stage declares for itself, so there is nothing left for a config-level sync/async switch to express.

Any parameter not in the table above is passed down as a sequence argument and read inside the sequence as %var.<name>%. This replaces the old with. prefix, which only existed because the old parameter model could not tell declared parameters from extra ones. It can now.

yaml
action:
  templates:
    burn_tick:
      - 'inherited | damage amount=1'
      - 'inherited | spawn_particle particle=flame count=5'
    buff_pulse:
      - 'self | send_action_bar text="<gold>Blessing: %var.label%</gold>"'

actions:
  # Once a second, ten times
  - 'looking_at | keep | start_task sequence=burn_tick times=10 interval=20t key=burn_%player_name%'
  # With a sequence argument, stopping on death
  - 'self | start_task sequence=buff_pulse times=30 interval=1s key=buff stop_when_dead=true label=Holy_Blessing'
  # Stop as soon as the condition no longer holds
  - 'self | start_task sequence=buff_pulse times=100 interval=20t stop_when=%player_world%!="world_arena"'

stop_task

Cancels running tasks by key. Target requirement NONE.

ParameterTypeRequiredDefaultDescription
keySTRINGYesTask key to cancel.
matchSTRINGNoexactMatch mode: exact or prefix.

Cancelling nothing counts as a skip, not a failure. Config routinely cancels a task that may not be running — cleaning up a buff loop on an event that fires in both cases, for instance — and treating that as an error would fill the log with correct configuration.

yaml
actions:
  - 'stop_task key=burn_%player_name%'
  - 'stop_task key=buff_ match=prefix'

Scripts

The three js_* stages run GraalJS scripts. They share the same parameters and differ only in target requirement and execution domain, so picking the wrong one means the script touches Bukkit API from the wrong thread.

ParameterTypeRequiredDefaultDescription
codeSTRINGYesJavaScript source.
timeoutINTEGERNo5000Timeout in milliseconds.

A timeout of zero or less falls back to the default. Empty code, a timeout, and an interruption all count as skipped rather than failed; only a thrown script error fails the stage.

The script's evaluated value is written to a single key, script_result, in that stage's own outcome data. It does not become a pipeline variable, so later stages cannot read %var.script_result%.

js_compute

Pure computation with no Bukkit state access. Target requirement NONE, execution domain ASYNC_COMPUTE. Binds context, plus player when the current target is a player.

Because it runs on an async thread, the script must not touch Bukkit state.

Inside the script, context.getVariable(name) and context.hasVariable(name) read pipeline variables.

yaml
actions:
  - 'self | set level=%player_level% | js_compute code="context.getVariable(''level'') * 10"'

js_entity

Scripts that act on a player or entity. Target requirement REQUIRED_ENTITY, execution domain CONTEXT_ENTITY. Binds player and context; skipped when the target is not a player.

js_location

Scripts that act on a block or location. Target requirement REQUIRED_LOCATION, execution domain LOCATION_REGION. Binds location and context, plus a read-only player when the current target is a player.

MythicMobs and skills

cast_mythic_skill and cast_skill come from the EmakiSkills module and are not CoreLib built-ins. For their parameters and behaviour, see Skills CoreLib Actions.

Listing and running pipelines from commands

Administrators can inspect the current registry and run a pipeline by hand from inside the server:

text
/corelib action list
/corelib action run <pipeline text>

The same subcommand is available through /emakicorelib action, /emakicore action, and the plural alias actions. It reuses the emakicorelib.admin permission. Console execution has no player context, so stages needing an entity target skip through the normal target-requirement path. The run keyword is optional: /corelib action self | heal amount=4 is equivalent to spelling it out.

action list groups ids by role (source / gate / action) and shows the owning plugin. Grouping rather than a flat list is deliberate: an id is only legal in one position within a pipeline, and a flat list cannot tell an operator where it may be used.

action run compiles before it executes, so a syntax error or unknown stage name arrives with its own diagnostic instead of a vague execution failure. Only the first diagnostic is shown — the compiler reports every problem on a line, and the rest are usually consequences of the first. When execution finishes it reports the pipeline's overall result, naming the failing stage on failure, because a hand-typed pipeline is normally being debugged and the failing stage is the answer.

Where to find sub-plugin stages

CoreLib owns the stage registry and the pipeline engine. Business plugins may append their own sources, gates, and actions to that same registry when they enable. To keep this page from becoming a stale catalogue of every plugin's stage parameters, it documents only CoreLib built-ins.

Stages registered by sub-plugins live on those plugins' own action pages:

Source moduleDocumentation
EmakiAttributeAttribute CoreLib Actions
EmakiForgeForge CoreLib Actions
EmakiStrengthenStrengthen CoreLib Actions
EmakiCookingCooking CoreLib Actions
EmakiGemGem CoreLib Actions
EmakiLevelLevel CoreLib Actions
EmakiSkillsSkills CoreLib Actions
EmakiItemItem CoreLib Actions
EmakiCodexCodex CoreLib Actions

When a stage comes from a business plugin, its parameters, context variables, and execution timing are defined by that plugin's documentation.

Registering stages from third-party plugins

External developers can depend on emaki-corelib-api alone and register custom stages into CoreLib's shared registry through the method matching the role:

  • EmakiCoreLibApi.registerActionStage(plugin, stage) — register an action
  • EmakiCoreLibApi.registerActionSource(plugin, source) — register a source
  • EmakiCoreLibApi.registerActionGate(plugin, gate) — register a gate

CoreLib automatically revokes stages and rebuild callbacks when the owner plugin is disabled; callers should still close returned handles during normal lifecycle cleanup.

An action implements CoreActionStage and declares id, category, description, parameters, targetRequirement, requiredContext, and executionTarget. CoreLib reuses the same lexer, grammar, parameter validation, placeholder rendering, scheduling, and debug output.

executionTarget has no default implementation, and that is intentional: a stage has to state which thread it runs on rather than inheriting a vague default. requiredContext is the mechanism that turns "missing an upstream stage" into a load-time error — send_item declares that it needs the pipeline item, so the validator catches a missing create_item while the config is loading.

Minimal example:

java
CoreStageRegistration registration = EmakiCoreLibApi.registerActionStage(plugin, new CoreActionStage() {
    @Override
    public String id() {
        return "my_custom_stage";
    }

    @Override
    public String category() {
        return "myplugin";
    }

    @Override
    public String description() {
        return "Runs my plugin's custom effect.";
    }

    @Override
    public List<CoreStageParameter> parameters() {
        return List.of(CoreStageParameter.required("value", CoreStageParameterType.STRING, "Custom value"));
    }

    @Override
    public CoreTargetRequirement targetRequirement() {
        return CoreTargetRequirement.REQUIRED_ENTITY;
    }

    @Override
    public CoreActionExecutionTarget executionTarget(CoreStagePlanningContext context) {
        // Pick the entity's own thread when you need main-thread Bukkit APIs.
        return CoreActionExecutionTarget.contextEntity();
    }

    @Override
    public CoreActionOutcome execute(CoreStageContext context, CoreResolvedArguments arguments) {
        // Your plugin logic goes here.
        return CoreActionOutcome.success();
    }
});

// On plugin disable:
registration.close();

Stage ID reference

source

idPurpose
selfThe caster (the default when omitted)
inheritedThe target flow passed in by the caller
triggerThe entity named by the trigger
originThe pipeline's reference point location
looking_atThe entity under the crosshair
nearbyEntities in range
nearby_playersPlayers in range
offsetA location offset from the reference point
atAn absolute or relative coordinate
player_by_nameA player named by name or UUID

gate

idPurpose
whereClears the flow when the condition is false
chanceEnds the pipeline when the roll fails
limitKeeps the first N targets
sort_bySorts by distance or health
setWrites %var.*% pipeline variables
keepRecords the flow for the next phase to inherit
stopEnds the pipeline here
create_itemBuilds and publishes the pipeline item
afterDelays every following stage
everyRepeats every following stage on an interval

action

idCategoryTarget requirementPurpose
send_messagemessageREQUIRED_ENTITYSend a chat message
send_action_barmessageREQUIRED_ENTITYSend an action bar
send_titlemessageREQUIRED_ENTITYShow a title
broadcast_messagemessageNONEBroadcast server-wide
play_soundfeedbackREQUIRED_ENTITYPlay a sound
spawn_particlefeedbackREQUIRED_ANYSpawn particles
boss_bar_showfeedbackREQUIRED_ENTITYShow a boss bar
boss_bar_hidefeedbackREQUIRED_ENTITYHide a boss bar
healentityREQUIRED_ENTITYRestore health
damageentityREQUIRED_ENTITYRemove health
set_healthentityREQUIRED_ENTITYSet health
feedentityREQUIRED_ENTITYRestore food
igniteentityREQUIRED_ENTITYSet on fire
extinguishentityREQUIRED_ENTITYPut out fire
kill_entityentityREQUIRED_ENTITYRemove an entity
projectilecombatOPTIONALLaunch a self-driven projectile
give_potion_effectentityREQUIRED_ENTITYAdd a potion effect
remove_potion_effectentityREQUIRED_ENTITYRemove a potion effect
clear_potion_effectsentityREQUIRED_ENTITYClear all potion effects
send_itemitemREQUIRED_ENTITYSend the pipeline item
give_itemitemREQUIRED_ENTITYGive an item by source
set_itemitemREQUIRED_ENTITYSet a slot's item
clear_itemitemREQUIRED_ENTITYClear a slot's item
take_itemitemREQUIRED_ENTITYRemove items from inventory
drop_itemitemREQUIRED_ANYDrop an item at the target
repair_itemitemREQUIRED_ENTITYRepair durability
damage_itemitemREQUIRED_ENTITYAdd durability damage
place_blockworldREQUIRED_ANYPlace a block from an item source
set_blockworldREQUIRED_ANYSet a block by material
break_blockworldREQUIRED_ANYBreak a block
explosionworldREQUIRED_ANYCreate an explosion
spawn_entityentityREQUIRED_ANYSpawn entities
teleportentityREQUIRED_ENTITYTeleport
give_moneyeconomyREQUIRED_ENTITYAdd money
take_moneyeconomyREQUIRED_ENTITYTake money
set_moneyeconomyREQUIRED_ENTITYSet balance
give_expplayerREQUIRED_ENTITYAdd experience
take_expplayerREQUIRED_ENTITYRemove experience
set_expplayerREQUIRED_ENTITYSet experience
run_command_as_playercommandREQUIRED_ENTITYRun as the player
run_command_as_opcommandREQUIRED_ENTITYRun as temporary OP
run_command_as_consolecommandNONERun as the console
start_tasktaskOPTIONALStart a repeating sequence
stop_tasktaskNONECancel tasks by key
js_computescriptNONEPure-computation JS script
js_entityscriptREQUIRED_ENTITYJS script acting on an entity
js_locationscriptREQUIRED_LOCATIONJS script acting on a location

Troubleshooting

Unknown stage name at load time. Check that the id is underscore-separated: it is send_message, not sendmessage. Confirm the id exists with /corelib action list, and confirm it appears in the right position — a source cannot sit where an action belongs.

Right stage name, but "unknown parameter". A few parameter names changed too. Item sources are uniformly item_source (the source and item aliases are gone), set_block has no block alias, and extra start_task arguments are written directly without a with. prefix.

The pipeline was skipped without an error. An unmet target requirement skips rather than fails. The usual cause is a missing source that fell back to self, where self is empty without a player context — or a nearby that found nothing.

|| is not working in a branch condition. Logical OR must be written ||. A single | is read as a stage separator.

send_item errors at load time. The stage needs the pipeline item, so the same pipeline must have a create_item ahead of it.

times is reported as out of range. every ... times N is bounded by action.pipeline.max_repeat_times (default 100). For more repetitions, use start_task, whose count quota is governed separately by the task service.

Spaces in a value got split apart. Values containing spaces, MiniMessage tags, or commands must be quoted. Wrapping the whole pipeline line in single quotes in YAML is also worth doing, so : and # are not interpreted by YAML.

A complete example

A full post-forge success config, showing several stage types working together:

yaml
action:
  success:
    # Core feedback
    - 'self | send_title title="<gold>Forge complete</gold>" subtitle="<gray>Quality: %forge_quality%</gray>" fade_in=5t stay=40t fade_out=10t'
    - 'self | play_sound sound=minecraft:block.anvil.use volume=1 pitch=1.2'
    - 'self | spawn_particle particle=happy_villager count=20 offset_x=0.5 offset_y=0.5 offset_z=0.5'

    # Economy reward: a 10% chance to refund part of the cost
    - 'self | chance 10% | give_money amount=50 provider=vault | send_message text="<yellow>Lucky! 50 coins refunded.</yellow>"'

    # Experience reward
    - 'self | give_exp amount=30 mode=points'

    # Extra reward for high quality
    - 'if %forge_quality%=="epic" [ self | send_message text="<light_purple>Epic quality! Bonus strengthen stone.</light_purple>" | create_item item_source=emakiitem-strengthen_stone amount=1 | send_item ]'

    # Server broadcast, legendary only
    - 'if %forge_quality%=="legendary" [ broadcast_message text="<gold>%player_name% forged legendary gear!</gold>" ]'

    # Delayed reminder
    - 'self | after 2s | send_message text="<gray>Your gear is updated; check your inventory.</gray>"'

Note the second reward line: once chance passes, both give_money and send_message further along the same pipeline run. The old syntax needed the chance written on both lines, rolling twice independently, which could pay out the money without showing the message. One pipeline rolls once, and every stage after it shares that result.