Custom Functions
You can register new function types that become available to every menu, dialog, item name/lore, action argument, and requirement value via the inline $name(...) syntax. See Functions for the YAML-side reference.
Step 1 — Implement PaperFunction
Functions are records implementing PaperFunction. The record components are the call's arguments, wrapped in PlaceholderWrapper<T> so each is resolved lazily inside the call's placeholder context.
Argument resolution
Component type | Behaviour |
|---|---|
| Argument resolved and parsed as int. |
| Resolved as a generic |
| Resolved as a string. |
|
|
| Optional — missing/blank arguments become |
Always resolve the wrapper through the context (context.parseWrapper(wrapper)) — never call wrapper.get() directly, since only the context knows the player and placeholder parser for the running invocation.
Return value
In most contexts the returned Object is converted to a string via String.valueOf(result) and substituted into the surrounding placeholder string. When a standalone $fn(...) call is the entire value of a text/component field (menu and item names/lore, titles, dialog labels — anywhere ComponentWrapper runs), the raw return value is integrated type-aware instead: an Adventure ComponentLike is spliced in directly, a StyleBuilderApplicable is applied as styling, a single-element List is unwrapped, and a nested PlaceholderWrapper is resolved one level; anything else falls back to String.valueOf. This lets a custom function return rich text rather than its toString(). Returning null still produces the literal "null", so prefer throwing ExecutableRunException for error cases.
Step 2 — Register the Function
Call this during your plugin's onEnable:
Use registerFunctionGlobally to expose the function to every plugin on the server (used by built-ins like add, round, min). Use the per-plugin registerFunction when the function only makes sense inside your own menus.
The first argument is the function name used in YAML. It may match [a-zA-Z0-9_-]+ — letters, digits, underscores, and hyphens — so dash-named functions (e.g. the built-ins format-number, format-date, and random-int) are callable inline via $name(...).
Step 3 — Use in YAML
The arguments %variables_qty%, %parameters_price%, and the literal 64000 are wrapped into PlaceholderWrapper<Number> components, resolved against the menu's placeholder context, and passed to MultiplyFunction.run.
Nullable Arguments
Mark optional components with @Nullable. The wrapper itself becomes null when the caller omits the argument, so the check is on the wrapper, not on the resolved value:
Error Handling
Throw ExecutableRunException for any user-visible problem (bad numeric input, invalid pattern, missing required state). The caller sees the function call replaced with UNPARSED_FUNCTION and an InvalidWrapException is logged with the failing call's literal text — making it easy to trace which YAML produced the failure.
Built-in Examples
The Paper module ships these implementations as a reference — read them when in doubt about idiomatic function design:
AddFunction,SubtractFunction— required + required + optional cap/floor.IncrementFunction,DecrementFunction— single required value + optional bound, preserves number type.MinFunction,MaxFunction— null-tolerant binary numeric ops.RoundFunction— required value + optional decimal precision.FormatNumberFunction— required value + optionalDecimalFormatpattern (default0.00).FormatDateFunction— required epoch millis + optional mode keyword orDateTimeFormatterpattern.
All live under com.astralrealms.core.paper.model.function.impl and are registered globally in AstralCore.onEnable.