English
Action System
To write an action pipeline, describe line by line in YAML what should happen after a business event, then look up which stages the pipeline can use and what parameters each stage takes. The action system 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_valueKey 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="...", notsend_message(text="...") - Arguments come in two forms:
key=value, or a bare positional value. Bare values map onto positional parameters in declaration order, sochance 25%is the same aschance chance=25% - Stage names are lower-cased, so
Send_Messageandsend_messageare 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 intext="a | b"is not a separator - Outside quotes,
|,[, and]split tokens even without surrounding spaces:a|banda | bare 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:
| Role | Purpose | Examples |
|---|---|---|
| source | Produces the target flow. Usually the first stage | self, looking_at, nearby radius=5 |
| gate | Filters, sorts, controls timing, or writes context values | chance 25%, where ..., after 20t |
| action | Produces 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, so watch out for this default: 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
ifbody, with its own source and as many stages as you like weightselects exactly one body. If you want each outcome judged independently so several can fire at once, that is multiplechancelines, notweight- A weight of
0disables that branch; it can never be drawn. All weights being0is 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 weightandifshare the single nesting limitaction.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
You can define a group of pipelines you reuse as a named sequence and call it 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_loginSequences 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 you see errors for mutual recursion, calls to names that do not exist, and missing required arguments when the config loads, not at runtime
Named aliases (aliases)
Give a frequently used pipeline fragment an alias and drop it into any pipeline line:
yaml
action:
aliases:
buff_essentials: 'give_potion_effect effect=speed duration=30s amplifier=0 | give_potion_effect effect=jump_boost duration=30s amplifier=0'
punish_msg: 'send_message text="<red>Operation refused</red>"'text
self | buff_essentials | damage amount=1
damage amount=3 on_error [ punish_msg ]Rules:
- Aliases expand into their original tokens at compile time; an alias may reference another alias (up to 8 levels; a cycle reports the
alias_cyclediagnostic); - The reserved words
if/else/run/weight/on_error/union/parse_ifare never shadowed by an alias; - An alias whose name matches a registered stage id is skipped with a startup warning (the original stage is never silently swallowed);
- Expanded column numbers shift to the alias position, so diagnostics still point at what you actually typed
Source union (union)
A pipeline may only contain one source stage; union [ ... ] merges several sources' results into one deduplicated target flow:
text
union [ all_players | nearby type=zombie radius=16 ] | filter 'health_percent<=50' | damage amount=2- Only sources may appear inside
union [ ]; an action or gate reportsunion_only_sources; - A source that matches nothing does not fail the whole union — only all-empty yields an empty flow;
- Merging uses a
LinkedHashSet(an entity selected by two sources appears once); unionpasses validation while the oldmultiple_sourcesdiagnostic still rejects "two bare source stages" — seeing that error means switch tounion
Compile-time branch (parse_if)
if evaluates at runtime; parse_if evaluates at compile time — when the condition is false the whole block never enters the compiled output, saving both parsing and runtime checks in long nested pipelines:
text
parse_if true [ send_message text="this block is compiled in" ]
parse_if false [ send_message text="discarded at load time" ]- The condition must be statically evaluable; a
%...%placeholder reportsparse_if_placeholder(that is a runtime concern — useif); - A condition the engine cannot resolve to a boolean reports
parse_if_not_evaluable; - When the condition is false the block is skipped at parse time, without shifting later tokens' columns
Error control flow (on_error and retry)
on_error [ ... ] routes a failed action into a handler; retry= / retry_delay= replays a stage with backoff after failure:
text
damage amount=3 on_error [ send_message text="<red>Damage failed: %var.error%</red>" ]
take_item item=sword amount=1 retry=3 retry_delay=1s%var.error%expands to one of the seven failure kinds (INVALID_CONFIG/MISSING_CONTEXT/WRONG_THREAD/TIMEOUT/OWNER_DISABLED/INTERNAL_ERROR/REJECTED);%var.error_reason%carries the detailed reason key;- A handler cannot nest another
on_error(caught at compile time asnested_on_error; split into several pipelines for deeper handling); retrycounts are capped byaction.pipeline.max_retry_times;retry_delayaccepts1s/20tforms;- Parentheses
( )afteron_errordo nothing — the handler must be wrapped in[ ]
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. Shipped defaults:
yaml
action:
pipeline:
max_repeat_times: 100
max_sequence_depth: 8
max_branch_depth: 16
max_retry_times: 5| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
action.pipeline.max_repeat_times | integer | No | 100 | positive integer (<=0 falls back) | Upper bound for every <interval> times <n>; exceeding it rejects the entry |
action.pipeline.max_sequence_depth | integer | No | 8 | positive integer (<=0 falls back) | Maximum run nesting depth |
action.pipeline.max_branch_depth | integer | No | 16 | positive integer (<=0 falls back) | Maximum nesting depth shared by if, weight and on_error |
action.pipeline.max_retry_times | integer | No | 5 | positive integer (<=0 falls back) | Upper bound for a stage's retry= count; exceeding it reports retry_limit_exceeded |
Example: allow up to 500 repeats and deeper weight nesting
yaml
action:
pipeline:
max_repeat_times: 500
max_branch_depth: 24Common parameters (apply to every stage)
The three entries below are not declared by any individual stage; the compiler strips them out of the stage's argument list during parsing, so they work on source, gate, and action alike, and you can append them after the stage name as needed:
| Parameter | Type | Default | Purpose |
|---|---|---|---|
on_error [ ... ] | branch body | none | Runs the branch body (a full pipeline) when this stage fails; the body reads %var.error% (one of seven failure kinds) and %var.error_reason% (the detailed reason key). Nesting on_error inside the body is rejected at compile time (nested_on_error) |
retry= | INTEGER | no retry | Replay count after a failure, capped by action.pipeline.max_retry_times (default 5); exceeding it raises retry_limit_exceeded. A literal must be a non-negative integer, otherwise it parses as -1 and triggers a diagnostic |
retry_delay= | DURATION | immediate | Backoff delay before each replay; accepts 20t / 1s / 500ms. Negative values are treated as 0. Has no effect without retry= |
Usage rules:
- Decorators sit in the argument area after the stage name; the compiler strips them before validating the stage's own parameters, so they never raise
unknown_argument. Example:damage amount=3 on_error [ send_message text="failed" ],take_item item=sword amount=1 retry=3 retry_delay=1s - Bracket syntax: the branch body must be paired
[ ]; theon_error ( ... )form with parentheses is invalid - Stage and parameter names are lowercased:
Send_Messageequalssend_message,AMOUNT=3equalsamount=3 - Two forms:
key=value, or a bare value (positional) matched against the stage's declared order —chance 25%is the same aschance chance=25% - Quoting and escapes: quote values containing spaces, MiniMessage tags,
|,[,],:or#. Single and double quotes both work; inside quotes\n,\t,\\,\",\'are escapes and a quoted|never splits the pipeline - Placeholders resolve late:
%var.*%/ PAPI placeholders render right before execution, so non-placeholder literals are type-checked at compile time while placeholder-bearing values defer the check to runtime; a mismatch raisesinvalid_argument_type - Undeclared means rejected: a parameter name the stage did not declare raises
unknown_argument(with candidate suggestions); a missing required parameter raisesmissing_required_argument. Run<any plugin root> debug action check "<pipeline line>"to get every diagnostic before loading
Each stage's dedicated parameters are documented in its section below; for the id list and jump targets see the Stage ID table. Full type definitions and examples are in Parameter types
Loop task limits
Long-running loops started by start_task are governed separately by action.loop.*, independently of the compile limits above. Shipped defaults:
yaml
action:
loop:
enabled: true
min_sync_interval: 5t
min_async_interval: 100ms
max_times: 7200
max_active_loops_total: 5000
max_active_loops_per_player: 16
max_active_loops_per_plugin: 1000
cancel_player_loops_on_quit: true
cancel_plugin_loops_on_disable: true| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
action.loop.enabled | boolean | No | true | true / false | Whether loop tasks may start at all |
action.loop.min_sync_interval | duration | No | 5t | >=1t (precheck) | Minimum interval for sync-domain loops |
action.loop.min_async_interval | duration | No | 100ms | >=1t (precheck) | Minimum interval for async-domain loops |
action.loop.max_times | integer | No | 7200 | >0 (precheck) | Maximum executions for a single loop task |
action.loop.max_active_loops_total | integer | No | 5000 | >0 (precheck) | Server-wide cap on active loop tasks |
action.loop.max_active_loops_per_player | integer | No | 16 | >0 (precheck) | Per-player cap on active loop tasks |
action.loop.max_active_loops_per_plugin | integer | No | 1000 | >0 (precheck) | Per-plugin cap on active loop tasks |
action.loop.cancel_player_loops_on_quit | boolean | No | true | true / false | Cancel a player's loop tasks when they quit |
action.loop.cancel_plugin_loops_on_disable | boolean | No | true | true / false | Cancel a plugin's loop tasks when it is disabled |
Both interval keys are time values (t/s/ms); the precheck requires at least 1 tick and reports an error otherwise. The loops themselves are created and cancelled by the start_task / stop_task stages (see Long-running tasks)
Example: raise the per-plugin loop cap and let loops survive a player quitting
yaml
action:
loop:
max_active_loops_per_plugin: 2000
cancel_player_loops_on_quit: falseParameter types
Stage parameters carry the types listed below, and the parameter tables below name them. Types are validated at load time, so a value you write wrong does not wait until runtime to surface
| Type | Meaning | Example |
|---|---|---|
| STRING | Text. Quote it when it contains spaces or special characters | text="<green>Success" |
| INTEGER | Whole number | amount=3, count=10 |
| DOUBLE | Decimal number | volume=0.8, amount=25.5 |
| BOOLEAN | true or false | delete_item=false, icon=true |
| DURATION | Time. Accepts t (ticks), s, ms; a bare number means ticks | 20t, 1s, 500ms |
| PERCENTAGE | Probability. Accepts percentages, decimals, and fractions | 50%, 0.5, 1/3 |
| ENTITY_TYPE | A Bukkit EntityType name | type=zombie |
| MATERIAL | A Bukkit Material name | material=stone |
| SOUND | A sound key: a Bukkit Sound enum name or namespace:key | sound=minecraft:block.anvil.use |
| EXPRESSION | Arithmetic 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.
sendmessagebecomessend_message,givemoneybecomesgive_money,runcommandasconsolebecomesrun_command_as_console, and so on for every id @control prefixes became stages in the pipeline.@chance=25%becomeschance 25%,@delay=20tbecomesafter 20t, and@if=<condition>becomes anif <condition> [ ... ]branch.@ignore_failurehas 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:
selffor the caster (also the default when omitted),inheritedfor the targets your caller passed in
A few ids changed by more than an underscore:
| Old | Now |
|---|---|
ray | source looking_at |
loopsync / loopasync | start_task (sync/async is no longer a config option; see that stage) |
cancelloop | stop_task |
@template=name / usetemplate | run 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 |
castmythicskill | cast_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=8trigger
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=20looking_at
The entity under the caster's crosshair
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
range | DOUBLE | No | 5 | Ray length |
width | DOUBLE | No | 0.5 | Ray 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
radius | DOUBLE | No | 1 | Search radius |
limit | INTEGER | No | 1 | Maximum entities returned |
type | ENTITY_TYPE | No | "" | Entity type filter; empty means no filter |
include_players | BOOLEAN | No | false | Whether 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=5snearby_players
Players around the pipeline reference point
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
radius | DOUBLE | No | 1 | Search radius |
limit | INTEGER | No | 0 | Maximum 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
x | DOUBLE | No | 0 | X offset |
y | DOUBLE | No | 0 | Y offset |
z | DOUBLE | No | 0 | Z offset |
relative | BOOLEAN | No | false | Offset 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=falseat
An absolute coordinate, or one relative to the reference point
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
world | STRING | No | "" | World name; empty uses the reference point's world |
x | STRING | No | ~ | X coordinate, supports ~ |
y | STRING | No | ~ | Y coordinate, supports ~ |
z | STRING | No | ~ | 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_planksplayer_by_name
An online player identified by name or UUID
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | STRING | Yes | — | Player 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=500select
Picks targets by a configured selector definition; see Target selectors below
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | STRING | Yes | — | Selector id defined under action.selectors |
name is positional, so name= can be dropped
text
select nearby_elites | damage amount=5
select nearby_elites | play_sound sound=minecraft:entity.zombie.hurt volume=1 pitch=1all_players
All online players on the server. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
limit | INTEGER | No | integer | 0 | Max players; 0 means no limit |
text
all_players | send_message text="example"dropped_items
Dropped items around the pipeline origin. Execution domain LOCATION_REGION
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
radius | DOUBLE | No | finite number | 8 | Search radius |
limit | INTEGER | No | integer | 0 | Max dropped items; 0 means no limit |
text
dropped_items | send_message text="example"faction_members
Online players in the given faction roster. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
member | STRING | Yes | string | — | Comma-separated list of member player names |
text
faction_members member=player1, player2 | send_message text="example"highest_block
The highest block in the origin's column. Execution domain LOCATION_REGION
This stage has no dedicated parameters
text
highest_block | send_message text="example"op_players
Online OP players on the server. Execution domain SERVER_GLOBAL
This stage has no dedicated parameters
text
op_players | send_message text="example"party
Online players holding the same party permission node. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
permission | STRING | Yes | string | — | Party permission node, e.g. emaki.party.alpha |
text
party permission=emaki.party.alpha | send_message text="example"players_by_gamemode
Online players in the given game mode. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
gamemode | STRING | Yes | survival / creative / adventure / spectator | — | Game mode, e.g. survival / creative / adventure / spectator |
limit | INTEGER | No | integer | 0 | Max players; 0 means no limit |
text
players_by_gamemode gamemode=survival | send_message text="example"players_in_region
Players inside the cube centered on the pipeline origin. Execution domain LOCATION_REGION
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
width | DOUBLE | No | finite number | 16 | Half width on the X axis |
height | DOUBLE | No | finite number | 16 | Half height on the Y axis |
depth | DOUBLE | No | finite number | 16 | Half depth on the Z axis |
limit | INTEGER | No | integer | 0 | Max players; 0 means no limit |
text
players_in_region | send_message text="example"players_with_material
Online players carrying an item of the given material. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
material | MATERIAL | Yes | Bukkit Material name | — | Item material |
limit | INTEGER | No | integer | 0 | Max players; 0 means no limit |
text
players_with_material material=diamond | send_message text="example"random
A random sample of online players. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
count | INTEGER | No | integer | 1 | Number of targets to sample |
text
random | send_message text="example"region
Entities in the cube whose corner is the pipeline origin. Execution domain LOCATION_REGION
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
width | DOUBLE | No | finite number | 8 | Half width on the X axis |
height | DOUBLE | No | finite number | 8 | Half height on the Y axis |
depth | DOUBLE | No | finite number | 8 | Half depth on the Z axis |
limit | INTEGER | No | integer | 0 | Max entities; 0 means no limit |
type | ENTITY_TYPE | No | Bukkit EntityType name | `` | Entity type filter |
include_players | BOOLEAN | No | true / false | true | Whether players are eligible targets |
text
region | send_message text="example"ring
Entities between the inner and outer radius. Execution domain LOCATION_REGION
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
min_radius | DOUBLE | No | finite number | 1 | Inner radius |
max_radius | DOUBLE | No | finite number | 8 | Outer radius |
limit | INTEGER | No | integer | 0 | Max entities; 0 means no limit |
type | ENTITY_TYPE | No | Bukkit EntityType name | `` | Entity type filter |
include_players | BOOLEAN | No | true / false | true | Whether players are eligible targets |
text
ring | send_message text="example"vertical_block
Block position offset vertically from the origin. Execution domain LOCATION_REGION
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
offset | INTEGER | No | integer | 1 | Blocks to move up (+) or down (-) |
text
vertical_block | send_message text="example"world_players
Online players in the given world. Execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
world | STRING | No | string | `` | World name; empty uses the origin's world |
limit | INTEGER | No | integer | 0 | Max players; 0 means no limit |
text
world_players | send_message text="example"Target selectors
A selector stores three decisions in configuration — which source picks the targets, which conditions they must satisfy, and how many are kept — so an action line only names it. You do not need one to filter targets: a single action line can already carry its own conditions through filter. A selector exists to pull the reusable part into the main configuration and keep it consistent across action lines
Two ways to use it
The same filtering task can be written straight into one action line, or defined once as a selector and then called. The inline form needs no configuration at all
| Form | How it is written | When to use it |
|---|---|---|
| Inline filter | a source stage + the filter or where gate | One-off conditions you want to write and tweak on the spot, without touching the main config |
| Named selector | action.selectors.<id> in the main config + select <id> | One condition set reused by several action lines, or changed once for everything |
| Mixed | select <id> with inline argument overrides, then an extra filter | Reuse a baseline condition set and append conditions for this one action |
All three forms below perform the same task: deal 5 damage to every zombie within 16 blocks whose health is at or below 50%
Inline form (no pre-configuration)
The source, the filtering gate, and the action all sit on one line. Entity type goes to the source's own type parameter; the health ratio goes to filter's predicate string:
text
nearby radius=16 limit=5 type=zombie | filter 'health_percent<=50' | damage amount=5You can also fold the type condition into the predicate string and let filter do all the narrowing:
text
nearby radius=16 limit=5 | filter 'entity_type=ZOMBIE health_percent<=50' | damage amount=5filter's positional argument must be quoted. Inside the predicate string a space means AND, || means OR, a leading ! means NOT, parentheses group, and a comma separates values. The full rule set and every accepted key are under filter
If you would rather not learn the predicate syntax, use where with an expression; it is evaluated per target after %target_*% placeholders are expanded:
text
nearby radius=16 limit=5 type=zombie | where %target_health_percent%<=50 | damage amount=5where takes exactly one expression, so multiple dimensions have to be chained with &&, which reads worse than a predicate string
Named selector form (configure first, then call)
Put the same conditions into action.selectors.<id> in the main config:
yaml
action:
selectors:
low_health_zombies:
source: nearby
arguments:
radius: 16
limit: 5
type: zombie
condition:
predicates: 'health_percent<=50'The action line is then just the name:
text
select low_health_zombies | damage amount=5Mixed form (reuse plus inline additions)
The selector supplies the baseline; the action line overrides the referenced source's arguments, or adds one-off conditions with filter / where:
text
select low_health_zombies radius=24
select low_health_zombies radius=24 | filter 'level>=30'Inline arguments must be declared by the referenced source stage (nearby declares radius, limit, type, include_players); a misspelled name returns unknown_argument instead of being silently ignored. A selector's own limit can only be set in the configuration
Choosing between them
Both forms share one evaluation stack: a filter predicate string and a selector's condition.predicates are exactly the same syntax, and the predicate keys are the same names as the typed nodes in condition.entries
- The condition is used once, or you want to tune it for this one action → inline
filter/where - The same condition set appears in recipes, GUIs and triggers, and you want one place to change it → a named selector
- You need the
at_least/exactlycounting modes, orinvalid_as_failure: false(let undecidable targets through) → define a selector in the main config;filteris fixed to exclude anything undecidable
Defining a selector
Selectors live under action.selectors.<id> in the CoreLib main configuration:
yaml
action:
selectors:
nearby_elites:
source: nearby
arguments:
radius: 16
limit: 5
include_players: false
limit: 2
condition:
type: all_of
invalid_as_failure: true
entries:
- type: entity_type
value: [ZOMBIE, SKELETON]
- type: health_percent
op: "<="
value: 50| Field | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
source | string | Yes | — | a registered source id (never select itself) | Source id to run, including sources registered by other modules |
arguments | section | No | {} | scalar values | Arguments for that source, written like action-line arguments; scalars only |
limit | integer | No | 0 | non-negative integer; 0 means no truncation | Truncates after filtering; 0 keeps everything |
condition | section | No | {} | a condition group (type / entries / required_count / predicates / invalid_as_failure) | See the condition group syntax below; without it every selected target is kept |
source may not be select itself; that is rejected at load time. The selector inherits the referenced source's thread domain: referencing nearby still runs in the location region and never reads across regions
Action lines call it as select <selector id>, and selector ids are case-insensitive. Arguments of the referenced source can be overridden inline — write only what changes; everything else comes from the main configuration:
text
select low_health_elites
select low_health_elites radius=24 limit=8
select low_health_elites radius=24 | filter 'level>=30'Inline arguments must be declared by the referenced source; a misspelled name answers unknown_argument instead of being silently ignored. The selector's own limit stays configuration-only. To stack more conditions, append filter or where rather than going back to the main configuration
Condition group syntax
condition matches the condition system: entries lists condition nodes, type selects the combination, and required_count drives the counting modes
A condition can also be written as a one-line predicate string (exactly the filter grammar); use the predicates field instead of entries:
yaml
condition:
predicates: 'entity_type=ZOMBIE health_percent<=50'
invalid_as_failure: falsepredicates and entries are mutually exclusive; writing both is rejected at load time rather than silently ignoring one of them. Use predicates for simple conditions and structured entries when you need at_least/exactly or deep nesting
| Combination | Meaning | Alias |
|---|---|---|
all_of | Every entry must hold | and |
any_of | At least one entry must hold | or |
none_of | No entry may hold | not |
at_least | At least required_count hold | — |
exactly | Exactly required_count hold | — |
Write an object containing entries inside entries to nest a group; the nested group declares its own type:
yaml
condition:
type: all_of
entries:
- type: any_of
entries:
- type: permission
value: emaki.admin
- type: level
op: ">="
value: 30
- type: none_of
entries:
- type: mythicmobs
id: SkeletonKingCondition nodes
Nodes come in four kinds: expression nodes (a bare string or type: expression), built-in typed nodes, registered mob-system nodes, and third-party registered types. Resolution order is built-ins, then mob systems, then third-party conditions
Built-in typed nodes:
type | Tested value | Fields |
|---|---|---|
entity_type | Entity type | value: type name or list; any match passes |
player | Whether it is a player | value: true / false |
dead | Whether it is dead | value: true / false |
world | Current world | value: world name or namespace:key |
health | Current health | op + value |
health_percent | Health as a percentage | op + value |
distance | Distance from the origin | op + value |
level | Player experience level | op + value |
food | Player food level | op + value |
gamemode | Player game mode | value: survival / creative / adventure / spectator |
permission | Player permissions | value: node or list; any match passes |
potion_effect | Active effects | effect: effect name or list; min_amplifier defaults to 0 |
scoreboard_tag | Scoreboard tags | value: tag or list |
state | Entity boolean state | key: state name; value: expected result, default true |
op is required for numeric nodes and accepts ==, !=, <, <=, >, >=. Valid state keys are sneaking, sprinting, flying, gliding, swimming, in_water, on_ground; sprinting and flying are answerable for players only
Mob-system nodes use the system id as type, id to filter mob ids (a list is allowed), and op + level to filter levels (a system without levels always reports 0):
yaml
condition:
entries:
- type: mythicmobs
id: [SkeletonKing, ZombieLord]
op: ">="
level: 5
- type: emakimobs
id: elite_zombiemythicmobs is recognised by EmakiCoreLib itself; emakimobs is registered by EmakiMobs when it enables. Any other mob system can contribute its identifier through EmakiCoreLibApi.registerTargetIdentityProvider(plugin, provider). When a system is not registered the node is unanswered; when it is registered but the entity does not belong to it, the node is simply not satisfied
Expression nodes and target placeholders
Expression nodes work as usual and additionally receive %target_-prefixed placeholders:
| Placeholder | Meaning |
|---|---|
%target_type% | Entity type name |
%target_name% | Entity name |
%target_world% | World name |
%target_health% | Current health |
%target_max_health% | Maximum health |
%target_health_percent% | Health as a percentage |
%target_distance% | Distance from the pipeline origin |
%target_level% / %target_food% / %target_gamemode% | Player experience level, food level, game mode |
%target_permission_<node>% | Whether the player holds that permission |
%target_tag_<tag>% | Whether the scoreboard tag is present |
%target_state_<state>% | Whether that state holds; unchanged when the state does not apply to the target |
%target_effect_<effect>% | Effect amplifier (0 is level I), empty when the effect is absent |
%target_<system>_id% | Mob-system id, such as %target_mythicmobs_id% |
%target_<system>_level% | Mob-system level |
Placeholders that cannot be answered stay as written, so the expression fails to evaluate and follows the rules below
yaml
condition:
entries:
- '%target_health_percent% <= 30 && %target_distance% <= 8'Unanswered conditions and invalid_as_failure
A condition node answers one of three ways: satisfied, not satisfied, or unanswered. Unanswered typically means a player-only condition on a non-player, an absent mob system, a numeric node missing op or value, or an expression that will not evaluate
invalid_as_failure decides how an unanswered node joins the combination, exactly as in the condition system:
true(default): any unanswered node fails the whole group, so the target is dropped. This is the safe direction — a mistyped configuration excludes targets instead of letting the wrong ones throughfalse: unanswered nodes are skipped and do not count toward the required amount; when every node is skipped the group counts as satisfied
Examples
Low-health elites:
yaml
action:
selectors:
low_health_elites:
source: nearby
arguments:
radius: 20
limit: 10
condition:
type: all_of
entries:
- type: entity_type
value: [ZOMBIE, HUSK, DROWNED]
- type: health_percent
op: "<="
value: 40
- type: state
key: on_groundNearby players that qualify for a reward:
yaml
action:
selectors:
reward_candidates:
source: nearby_players
arguments:
radius: 32
limit: 5
condition:
type: all_of
entries:
- type: any_of
entries:
- type: permission
value: emaki.vip
- type: level
op: ">="
value: 30
- type: none_of
entries:
- type: gamemode
value: creativeMythicMobs and EmakiMobs identities:
yaml
action:
selectors:
boss_like:
source: nearby
arguments:
radius: 24
condition:
type: any_of
entries:
- type: mythicmobs
op: ">="
level: 10
- type: emakimobs
id: bone_lordUsed from an action line:
text
select low_health_elites | kill_entity
select reward_candidates | give_money amount=100Load-time checks
Config precheck validates selector structure and reports the offending path (for example action.selectors.low_health_elites.condition.entries[1]): a missing source, source pointing at select itself, a source that is not currently registered, a numeric node missing op or value, and an unknown state name. The first two are errors, which fail the precheck and keep the previous working configuration; the rest are warnings
At runtime, a node type that is neither built-in, a registered system, nor a registered third-party condition logs one warning listing the available types and counts as unanswered
Third-party condition types
Modules and other plugins can register their own condition types and use them inside condition.entries:
java
EmakiCoreLibApi.registerTargetCondition(this, new CoreTargetCondition() {
@Override
public String id() {
return "myplugin_custom";
}
@Override
public CoreTargetOutcome test(CoreActionSubject subject,
CoreStageContext context,
CoreTargetConditionArguments arguments) {
return subject.entityOrNull() == null
? CoreTargetOutcome.UNKNOWN
: CoreTargetOutcome.PASS;
}
});Built-in ids cannot be shadowed — a colliding registration is refused with a stable reasonKey. Close the handle when the plugin disables; this registry is not rebuilt by a CoreLib reload
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
Drops every target whose condition does not hold
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
condition | STRING | Yes | — | Boolean condition |
condition is positional. Pipeline placeholders are substituted first and the expression is then evaluated per target, so it can use %var.<name>%, CoreLib's player placeholders, variables written by the calling module and PlaceholderAPI placeholders — plus the %target_*% placeholders listed under Target selectors
When the condition contains no %target_*% placeholder every target gets the same verdict, so the flow is kept or cleared as a whole — existing configurations behave exactly as before. Only a condition that reads target placeholders filters target by target
text
looking_at | where %target_health_percent% < 10 | kill_entity
nearby radius=6 limit=10 | where %player_world%=="world" | damage amount=4For anything more complex use the filter predicates; put conditions shared by several actions into action.selectors
filter
Filters targets by inline predicates, without defining a selector in the main configuration first. Its positional argument is one quoted predicate string:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
condition | STRING | Yes | — | The predicate string |
Predicate grammar:
| Rule | Form | Meaning |
|---|---|---|
| Conjunction | entity_type=ZOMBIE health_percent<=50 | Whitespace between items means AND |
| Disjunction | level>=30 || permission=emaki.vip | || means OR |
| Negation | !gamemode=creative | Leading ! means NOT |
| Grouping | (health<=30 || level>=50) entity_type=ZOMBIE | Parentheses nest freely |
| Value list | entity_type=ZOMBIE,SKELETON | Comma means "any of"; with !=, "none of" |
Available keys carry the same names as the action.selectors condition types, with no aliases:
| Key | Form | Meaning |
|---|---|---|
entity_type | entity_type=ZOMBIE,SKELETON | Entity type |
player / dead | player=true, dead=false | Whether it is a player / is dead |
world | world=world_nether | World name or namespace:key |
health | health<=10 | Current health |
health_percent | health_percent<=50 | Health as a percentage (0–100) |
distance | distance<=8 | Distance from the pipeline origin |
level / food | level>=30 | Player experience level / food level |
gamemode | gamemode!=creative | Player game mode |
permission | permission=emaki.vip | Permission node; a list means "any of" |
scoreboard_tag | scoreboard_tag=elite | Scoreboard tag |
state.<name> | state.sneaking, state.on_ground=false | Entity boolean state, expected true by default |
potion_effect.<name> | potion_effect.speed>=2 | Effect amplifier (0 is level I); >=0 means "has it" |
<mob system id> | mythicmobs=SkeletonKing, emakimobs=elite_zombie | Mob-system identity; a list is allowed |
<mob system id>_level | mythicmobs_level>=5 | Mob-system level |
Numeric keys require one of ==, !=, <, <=, >, >= — writing health without an operator is an error rather than a silent pass. The predicate string must be quoted: unquoted, entity_type=ZOMBIE is read as a stage argument and rejected as unknown. That failure is deliberate, not an inconvenience
text
nearby radius=12 | filter 'entity_type=ZOMBIE health_percent<=50' | damage amount=5
select low_health_elites | filter 'level>=30 || permission=emaki.vip' | give_money amount=100filter and where stack, filtering in pipeline order. Evaluation shares the same machinery as action.selectors condition groups; filter always behaves as invalid_as_failure: true, so an unanswered predicate (a player-only condition on a non-player, an absent mob system, an expression that will not evaluate) drops that target. When you need unanswered to pass instead, define a selector with invalid_as_failure: false
chance
Ends the pipeline when the roll fails
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
chance | PERCENTAGE | Yes | — | Probability, 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=vaultlimit
Keeps the first count targets in the flow, preserving order
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
count | INTEGER | Yes | — | How many targets to keep |
count is positional
text
nearby radius=15 limit=50 | sort_by distance | limit 3 | damage amount=12sort_by
Sorts the target flow by distance or health
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
key | STRING | Yes | — | distance or health |
order | STRING | No | asc | asc 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
item_source | STRING | No | "" | Item source |
amount | INTEGER | No | 1 | Item 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_itemafter
Delays every stage after it
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
delay | DURATION | Yes | — | Delay, 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
interval | DURATION | No | 1t | Interval, such as 20t or 1s |
times | INTEGER | No | 0 | Extra 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.5every suits short bursts inside one pipeline. For a long-running loop you can cancel by key, use start_task
distance_filter
Keep only targets within [min, max] distance of the origin. Thread requirement NEEDS_ENTITY_READ
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
min | DOUBLE | No | finite number | 0 | Minimum distance |
max | DOUBLE | No | finite number | 32 | Maximum distance |
text
self | distance_filter | send_message text="example"sample
Randomly sample count targets. Thread requirement PURE
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
count (positional) | INTEGER | Yes | integer | — | Number of targets to sample |
text
self | sample '3' | send_message text="example"shuffle
Randomly shuffle target order. Thread requirement PURE
This stage has no dedicated parameters
text
self | shuffle | send_message text="example"where_all
Keep targets satisfying every predicate; predicates separated by ||. Thread requirement NEEDS_ENTITY_READ
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
conditions (positional) | STRING | Yes | string | — | Multiple predicates separated by ` |
text
self | where_all 'player || health_percent<=50' | send_message text="example"where_any
Keep targets satisfying any predicate; predicates separated by ||. Thread requirement NEEDS_ENTITY_READ
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
conditions (positional) | STRING | Yes | string | — | Multiple predicates separated by ` |
text
self | where_any 'health_percent<=50 || dead' | send_message text="example"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, without failing the whole pipeline
NONE: needs no target at allOPTIONAL: runs either way, and still runs once with zero targetsREQUIRED_ENTITY: needs at least one entity targetREQUIRED_LOCATION: needs at least one location targetREQUIRED_ANY: needs at least one target, entity or location
Validation and auditing for numeric stages
Health, food, experience, money, and item-amount stages validate amount explicitly instead of clamping silently:
- An invalid value fails with
INVALID_CONFIGand carries that value in the message, so the problem is visible to the server owner rather than looking like a config that "mostly works" - Health, food, experience, and money stages require a finite, non-negative
amount(NaNandInfinityare invalid). Decrease stages (take_exp,take_money,take_item,take_food) and item-granting stages (give_item,set_item,drop_item) additionally require it to be greater than 0 - Insufficient funds, experience, items, or food return
REJECTEDwith the required and current amounts. Nothing is partially withdrawn, and a balance or count never goes negative - Every successful or failed numeric change is written to the CoreLib
DebugLoggermoduleaction.audit, withstage,operation, target,before/after,amount, source plugin, andphase; failure lines also carryreasonanddetails. See the global debug switch in Commands and permissions
Messages and feedback
send_message
Sends a MiniMessage chat message. Target requirement REQUIRED_ENTITY
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
text | STRING | Yes | — | Message 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
text | STRING | Yes | — | Action 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
title | STRING | Yes | — | Main title in MiniMessage |
subtitle | STRING | No | "" | Subtitle in MiniMessage |
fade_in | DURATION | No | 10t | Fade-in time |
stay | DURATION | No | 40t | Hold time |
fade_out | DURATION | No | 10t | Fade-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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
text | STRING | Yes | — | Broadcast 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sound | SOUND | Yes | — | Sound key: a Bukkit Sound enum name or a minecraft: style key |
volume | DOUBLE | No | 1 | Volume; above 1 increases audible range |
pitch | DOUBLE | No | 1 | Pitch, 0.5–2.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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
particle | STRING | Yes | — | Particle key (Bukkit Particle enum name) |
count | INTEGER | No | 1 | Particle count |
offset_x | DOUBLE | No | 0 | Spread along X |
offset_y | DOUBLE | No | 0 | Spread along Y |
offset_z | DOUBLE | No | 0 | Spread along Z |
extra | DOUBLE | No | 0 | Extra 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | STRING | Yes | — | Boss bar id, used to update or hide it later |
title | STRING | Yes | — | Bar title in MiniMessage |
progress | DOUBLE | No | 1 | Progress, from 0 to 1 |
color | STRING | No | purple | Bar color |
style | STRING | No | solid | Bar style |
flags | STRING | No | "" | 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | STRING | Yes | — | Boss bar id, or all to hide every bar |
yaml
actions:
- 'self | boss_bar_hide id=forge_progress'
- 'self | boss_bar_hide id=all'reset_title
Clear the title and subtitle on screen. Category message, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
This stage has no dedicated parameters
yaml
actions:
- 'self | reset_title'send_title_fade
Send a title with fade-in and fade-out timings. Category message, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
title | STRING | Yes | string | — | Title text |
subtitle | STRING | No | string | `` | Subtitle text |
fade_in | INTEGER | No | integer | 10 | Fade-in ticks |
stay | INTEGER | No | integer | 40 | Ticks to stay on screen |
fade_out | INTEGER | No | integer | 10 | Fade-out ticks |
yaml
actions:
- 'self | send_title_fade title=example'
- 'self | send_title_fade title=example subtitle='spawn_particle_at
Spawn particle effects at the given coordinates. Category visual, target NONE, execution domain LOCATION_REGION
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
particle | STRING | Yes | string | — | Particle type |
x | DOUBLE | No | finite number | 0 | X offset |
y | DOUBLE | No | finite number | 0 | Y offset |
z | DOUBLE | No | finite number | 0 | Z offset |
count | INTEGER | No | integer | 1 | Particle count |
yaml
actions:
- 'spawn_particle_at particle=flame'
- 'spawn_particle_at particle=flame x=0'Health and state
heal
Restores the target's health. Target requirement REQUIRED_ENTITY (it must be a living entity; non-living targets are skipped). Never exceeds max health
amount must be a finite, non-negative number; a negative or non-finite value fails the stage with INVALID_CONFIG instead of being treated as 0
IMPORTANT
This stage sets health directly and does not fire Bukkit's EntityRegainHealthEvent. No healing bonus or reduction applies
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | DOUBLE | Yes | — | Health 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
amount must be a finite, non-negative number; a negative or non-finite value fails the stage with INVALID_CONFIG instead of being treated as 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | DOUBLE | Yes | — | Damage 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
amount must be a finite, non-negative number; a negative or non-finite value fails the stage with INVALID_CONFIG
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | DOUBLE | Yes | — | Target 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | INTEGER | No | 20 | Food points restored |
saturation | DOUBLE | No | 0 | Saturation restored |
A negative amount fails the stage with INVALID_CONFIG; a negative saturation is treated as 0. Food and saturation never exceed 20
yaml
actions:
- 'self | feed'
- 'self | feed amount=6 saturation=3'take_food
Removes food from the target, optionally draining saturation as well. Target requirement REQUIRED_ENTITY
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | INTEGER | Yes | — | Food points to remove |
saturation | DOUBLE | No | 0 | Extra saturation points to remove |
amount must be greater than 0, otherwise the stage fails with INVALID_CONFIG. When the food level is insufficient it returns REJECTED (with the required and current values) and withdraws nothing; a negative saturation is treated as 0, and saturation floors at 0
yaml
actions:
- 'self | take_food amount=6'
# Drain two saturation points as well
- 'self | take_food amount=4 saturation=2'
# Insufficient food makes the stage reject instead of going negative
- 'self | take_food amount=20'ignite
Sets the target on fire. Target requirement REQUIRED_ENTITY
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
duration | DURATION | No | 5s | Burn 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
speed | DOUBLE | No | 1.5 | Blocks travelled per tick |
gravity | DOUBLE | No | 0.05 | Downward pull per tick |
lifetime | INTEGER | No | 60 | Maximum lifetime in ticks |
hit_radius | DOUBLE | No | 0.5 | Hit detection radius |
pierce | INTEGER | No | 0 | How many extra entities it passes through |
homing | BOOLEAN | No | false | Steer toward the current target |
homing_strength | DOUBLE | No | 0.1 | Homing turn strength |
particle | STRING | No | flame | Trail particle key |
damage | DOUBLE | No | 0 | Damage dealt on hit; 0 means none |
direction | STRING | No | look | Initial 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'set_absorption
Set the entity's absorption health. Category entity, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
amount | DOUBLE | Yes | finite number | — | Absorption health |
yaml
actions:
- 'self | set_absorption amount=1.0'set_arrows_in_body
Set the number of arrows stuck in the entity. Category entity, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
count | INTEGER | Yes | integer | — | Number of arrows |
yaml
actions:
- 'self | set_arrows_in_body count=3'set_collidable
Toggle collisions between mobs. Category entity, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
collidable | BOOLEAN | Yes | true / false | — | Whether collisions apply |
yaml
actions:
- 'self | set_collidable collidable=true'set_max_no_damage_ticks
Set the entity's damage-immunity cap in ticks. Category entity, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
ticks | INTEGER | Yes | integer | — | Damage-immunity cap |
yaml
actions:
- 'self | set_max_no_damage_ticks ticks=3'set_no_damage_ticks
Set the entity's damage-immunity duration in ticks. Category entity, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
ticks | INTEGER | Yes | integer | — | Damage-immunity duration in ticks |
yaml
actions:
- 'self | set_no_damage_ticks ticks=3'set_remaining_air
Set the entity's remaining air ticks. Category entity, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
ticks | INTEGER | Yes | integer | — | Remaining air in ticks |
yaml
actions:
- 'self | set_remaining_air ticks=200'Entity metadata
add_scoreboard_tag
Add a scoreboard tag to the entity. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
tag | STRING | Yes | string | — | Tag text |
yaml
actions:
- 'self | add_scoreboard_tag tag=example'remove_scoreboard_tag
Remove a scoreboard tag from the entity. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
tag | STRING | Yes | string | — | Tag text |
yaml
actions:
- 'self | remove_scoreboard_tag tag=example'set_ai
Toggle the mob's AI. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
enabled | BOOLEAN | Yes | true / false | — | Whether AI is enabled |
yaml
actions:
- 'self | set_ai enabled=true'set_can_pickup_items
Toggle whether the mob picks up dropped items. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
can_pickup | BOOLEAN | Yes | true / false | — | Whether items can be picked up |
yaml
actions:
- 'self | set_can_pickup_items can_pickup=true'set_fire_ticks
Set the burning duration in ticks (negative extinguishes). Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
ticks | INTEGER | Yes | integer | — | Burning duration in ticks |
yaml
actions:
- 'self | set_fire_ticks ticks=100'set_glowing
Toggle the glowing outline. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
glowing | BOOLEAN | Yes | true / false | — | Whether the entity glows |
yaml
actions:
- 'self | set_glowing glowing=true'set_gravity
Toggle gravity for the entity. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
gravity | BOOLEAN | Yes | true / false | — | Whether gravity applies |
yaml
actions:
- 'self | set_gravity gravity=true'set_invulnerable
Toggle invulnerability. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
invulnerable | BOOLEAN | Yes | true / false | — | Whether the entity is invulnerable |
yaml
actions:
- 'self | set_invulnerable invulnerable=true'set_name
Set the entity's custom name (MiniMessage). Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
name | STRING | Yes | string | — | Name text |
yaml
actions:
- 'self | set_name name=<gold>Boss</gold>'set_name_visible
Toggle whether the custom name is always visible. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
visible | BOOLEAN | Yes | true / false | — | Whether the name is always visible |
yaml
actions:
- 'self | set_name_visible visible=true'set_persistent
Toggle whether the entity survives a chunk unload. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
persistent | BOOLEAN | Yes | true / false | — | Whether to keep the entity |
yaml
actions:
- 'self | set_persistent persistent=true'set_remove_when_far_away
Toggle removal when too far from players. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
remove | BOOLEAN | Yes | true / false | — | Whether the mob is removed |
yaml
actions:
- 'self | set_remove_when_far_away remove=true'set_silent
Toggle entity silence. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
silent | BOOLEAN | Yes | true / false | — | Whether the entity is silent |
yaml
actions:
- 'self | set_silent silent=true'set_visible_by_default
Toggle default visibility to all players. Category meta, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
visible | BOOLEAN | Yes | true / false | — | Whether it is visible to all players by default |
yaml
actions:
- 'self | set_visible_by_default visible=true'Movement and velocity
add_velocity
Add a velocity vector to the entity (knockback/launch). Category movement, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
x | DOUBLE | Yes | finite number | — | Velocity delta along X |
y | DOUBLE | Yes | finite number | — | Velocity delta along Y |
z | DOUBLE | Yes | finite number | — | Velocity delta along Z |
yaml
actions:
- 'self | add_velocity x=1.0 y=1.0 z=1.0'knockback
Push the target away from the caster. Category movement, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
strength | DOUBLE | No | finite number | 1 | Knockback strength |
vertical | DOUBLE | No | finite number | 0.4 | Upward velocity |
yaml
actions:
- 'self | knockback'
- 'self | knockback strength=1'set_rotation
Set the entity's facing (yaw/pitch). Category movement, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
yaw | DOUBLE | Yes | finite number | — | Horizontal yaw angle |
pitch | DOUBLE | Yes | finite number | — | Pitch angle |
yaml
actions:
- 'self | set_rotation yaw=1.0 pitch=1.0'set_velocity
Set the entity's velocity vector. Category movement, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
x | DOUBLE | Yes | finite number | — | Velocity along X |
y | DOUBLE | Yes | finite number | — | Velocity along Y |
z | DOUBLE | Yes | finite number | — | Velocity along Z |
yaml
actions:
- 'self | set_velocity x=1.0 y=1.0 z=1.0'Potion effects
give_potion_effect
Adds a potion effect to the target. Target requirement REQUIRED_ENTITY
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
type | STRING | Yes | — | Effect type, written as speed, minecraft:strength, and so on |
level | INTEGER | Yes | — | Effect level, one-based. 1 maps to Bukkit amplifier 0 (in-game I) |
duration | DURATION | Yes | — | Duration, such as 100t, 5s, 10000ms |
ambient | BOOLEAN | No | false | Ambient effect (sparser, more transparent particles) |
particles | BOOLEAN | No | true | Show particles |
icon | BOOLEAN | No | true | Show 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
type | STRING | Yes | — | Effect 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,handoffhand,off_handhelmet,chestplate/chest,leggings/legs,boots- inventory indexes
0to35, also writable asslot_0orhotbar_0
Item-amount stages (give_item, set_item, take_item, drop_item) require an amount greater than 0, otherwise they fail with INVALID_CONFIG. take_item returns REJECTED (with the required and current values) when the inventory holds too few, and withdraws nothing. repair_item and damage_item treat amount differently — see their sections
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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
item_source | STRING | No | "" | Item source |
amount | INTEGER | No | 1 | Amount |
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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slot | STRING | No | mainhand | Inventory slot |
item_source | STRING | No | "" | Item source |
amount | INTEGER | No | 1 | Amount |
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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slot | STRING | Yes | — | Inventory slot |
item_source | STRING | No | "" | 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
item_source | STRING | No | "" | Item source to remove |
amount | INTEGER | No | 1 | Amount 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
item_source | STRING | No | "" | Item source |
amount | INTEGER | No | 1 | Item 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slot | STRING | No | mainhand | Inventory slot |
amount | INTEGER | No | 0 | Durability 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slot | STRING | No | mainhand | Inventory slot |
amount | INTEGER | No | 1 | Durability damage added |
delete_item | BOOLEAN | No | false | Remove the item when durability runs out |
A negative amount returns INVALID_CONFIG; an amount of 0 skips the stage without touching the item
yaml
actions:
- 'self | damage_item amount=10'
- 'self | damage_item slot=mainhand amount=100 delete_item=true'Player state
clear_inventory
Clear the player's inventory. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
This stage has no dedicated parameters
yaml
actions:
- 'self | clear_inventory'set_bed_spawn
Set the player's respawn point to the origin. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
This stage has no dedicated parameters
yaml
actions:
- 'self | set_bed_spawn'set_compass_target
Point the player's compass at the origin. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
This stage has no dedicated parameters
yaml
actions:
- 'self | set_compass_target'set_exp_to_level
Set the player's XP bar percentage directly. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
percent | INTEGER | Yes | 0 to 100 | — | XP bar percentage, 0-100 |
yaml
actions:
- 'self | set_exp_to_level percent=3'set_fly_speed
Set the player's flight speed. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
speed | DOUBLE | Yes | -1.0 to 1.0 | — | Flight speed, -1.0 to 1.0 |
yaml
actions:
- 'self | set_fly_speed speed=1.0'set_gamemode
Set the player's game mode. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
gamemode | STRING | Yes | survival / creative / adventure / spectator | — | survival / creative / adventure / spectator |
yaml
actions:
- 'self | set_gamemode gamemode=creative'set_item_cooldown
Set a cooldown for the given material on the player. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
material | MATERIAL | Yes | Bukkit Material name | — | Item material |
seconds | INTEGER | Yes | integer | — | Cooldown in seconds |
yaml
actions:
- 'self | set_item_cooldown material=golden_apple seconds=60'set_level
Set the player's experience level directly. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
level | INTEGER | Yes | integer | — | Experience level |
yaml
actions:
- 'self | set_level level=3'set_saturation
Set the player's saturation. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
value | DOUBLE | Yes | finite number | — | Saturation value |
yaml
actions:
- 'self | set_saturation value=1.0'set_walk_speed
Set the player's walk speed. Category player, target REQUIRED_ENTITY, execution domain CONTEXT_ENTITY
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
speed | DOUBLE | Yes | -1.0 to 1.0 | — | Walk speed, -1.0 to 1.0 |
yaml
actions:
- 'self | set_walk_speed speed=1.0'Blocks and world
place_block
Places the block for an item source at the target location. Target requirement REQUIRED_ANY
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
item_source | STRING | No | "" | 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
material | STRING | No | "" | Block material name |
block_data | STRING | No | "" | Bukkit BlockData string |
apply_physics | BOOLEAN | No | true | Trigger 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
drop_items | BOOLEAN | No | false | Drop the block's items |
apply_physics | BOOLEAN | No | true | Trigger 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
power | DOUBLE | No | 0 | Explosion power |
fire | BOOLEAN | No | false | Set fire |
break_blocks | BOOLEAN | No | false | Break 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
type | ENTITY_TYPE | Yes | — | Entity type |
count | INTEGER | No | 1 | How 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'set_difficulty
Set the world difficulty. Category world, target NONE, execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
difficulty | STRING | Yes | peaceful / easy / normal / hard | — | Difficulty: peaceful / easy / normal / hard |
yaml
actions:
- 'set_difficulty difficulty=normal'set_game_rule
Set a vanilla game rule of the world. Category world, target NONE, execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
rule | STRING | Yes | string | — | Game rule name |
value | STRING | Yes | true / false or an integer, per rule type | — | Rule value |
yaml
actions:
- 'set_game_rule rule=doDaylightCycle value=false'set_weather
Set the world weather. Category world, target NONE, execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
weather | STRING | Yes | clear / rain / thunder | — | Weather: clear / rain / thunder |
duration | INTEGER | No | integer | 0 | Duration in seconds; 0 means unlimited |
yaml
actions:
- 'set_weather weather=rain'
- 'set_weather weather=rain duration=0'set_world_spawn
Set the world spawn point to the origin. Category world, target NONE, execution domain LOCATION_REGION
This stage has no dedicated parameters
yaml
actions:
- 'set_world_spawn'set_world_time
Set the world time. Category world, target NONE, execution domain SERVER_GLOBAL
| Parameter | Type | Required | Range | Default | Description |
|---|---|---|---|---|---|
time | STRING | Yes | tick count or day / night | — | Time as tick count or day/night |
yaml
actions:
- 'set_world_time time=day'Teleport
teleport
Teleports the target to a coordinate. Target requirement REQUIRED_ENTITY
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
world | STRING | No | "" | Destination world; empty uses the target's current world |
x | STRING | No | ~ | X coordinate. Supports ~ relative notation (~5 means X+5) |
y | STRING | No | ~ | Y coordinate. Supports ~ |
z | STRING | No | ~ | Z coordinate. Supports ~ |
yaw | DOUBLE | No | target's current | Yaw (horizontal facing, 0–360). Omitted keeps the current facing |
pitch | DOUBLE | No | target's current | Pitch (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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | DOUBLE | Yes | — | Amount |
provider | STRING | No | auto | Economy provider: auto, vault, or excellenteconomy |
currency | STRING | No | "" | Currency id. Required when ExcellentEconomy runs multiple currencies |
amount must be a finite, non-negative number; NaN, Infinity, and negative values all return INVALID_CONFIG. give_money and take_money additionally require an amount greater than 0, while set_money allows 0 (used to clear a balance). An unavailable provider or an unknown currency is also treated as INVALID_CONFIG
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. An insufficient balance returns REJECTED (with the required and current values); the balance never goes negative and nothing is partially withdrawn
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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
amount | INTEGER | Yes | — | Experience points or levels |
mode | STRING | No | points | Mode: points (experience points) or levels |
give_exp and set_exp require a non-negative amount; a negative value returns INVALID_CONFIG. take_exp requires an amount greater than 0
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. When the target has too little, it returns REJECTED (with the mode plus the required and current values) and withdraws nothing; experience never goes negative. points mode measures total experience, levels mode measures levels
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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
command | STRING | Yes | — | The 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sequence | STRING | Yes | — | Name of the sequence to repeat |
times | INTEGER | No | 1 | How many times to run |
interval | DURATION | No | 20t | Interval between runs |
initial_delay | DURATION | No | 0t | Delay before the first run |
key | STRING | No | "" | Task key, used to cancel it later |
on_conflict | STRING | No | replace | Key conflict policy: replace, ignore, allow_duplicate |
stop_when_offline | BOOLEAN | No | true | Stop when the player logs off |
stop_when_dead | BOOLEAN | No | false | Stop when the player dies |
stop_when | STRING | No | "" | Stop once this condition holds |
stop_on_failure | BOOLEAN | No | false | Stop 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
key | STRING | Yes | — | Task key to cancel |
match | STRING | No | exact | Match 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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
code | STRING | Yes | — | JavaScript source |
timeout | INTEGER | No | 5000 | Timeout 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 avoid a stale catalogue of every plugin's stage parameters, only CoreLib built-ins are documented here
Stages registered by sub-plugins live on those plugins' own action pages:
| Source module | Documentation |
|---|---|
| EmakiAttribute | Attribute CoreLib Actions |
| EmakiForge | Forge CoreLib Actions |
| EmakiStrengthen | Strengthen CoreLib Actions |
| EmakiCooking | Cooking CoreLib Actions |
| EmakiGem | Gem CoreLib Actions |
| EmakiLevel | Level CoreLib Actions |
| EmakiSkills | Skills CoreLib Actions |
| EmakiItem | Item CoreLib Actions |
| EmakiCodex | Codex CoreLib Actions |
| EmakiStorage | Storage CoreLib Actions |
The 4 sources and 4 actions registered by EmakiMobs have no dedicated actions page yet; they are listed by ID and purpose on the Stage ID table only
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 actionEmakiCoreLibApi.registerActionSource(plugin, source)— register a sourceEmakiCoreLibApi.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
To look up all 218 stages (131 built into CoreLib — 25 source / 16 gate / 90 action — plus 87 registered by this repository's plugins) grouped with purpose text, registering plugin and jump targets, use the Stage ID table page. Per-stage dedicated parameters for built-in stages are the tables in each section below, stages registered by a plugin follow that plugin's own actions page as listed under Where to find sub-plugin stages, and the control decorators shared by all three roles are covered under Common parameters
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