Custom custom/code

Custom Code Node

Execute arbitrary Groovy code from inside your PNode graph. Access the full Bukkit/Paper API with up to 4 inputs and 4 outputs.

Security notice: Groovy code runs with full server permissions. Only give trusted users access to graphs containing Custom Code nodes.

Script Bindings

The following variables are available in every script:

VariableTypeDescription
in0–in3 Object Resolved input values.
out0–out3 Object Must be set by the script to pass data to output ports.
server org.bukkit.Server The Bukkit server instance.
logger java.util.logging.Logger The PNode plugin logger.
ctx dev.pnode.node.ExecutionContext The current graph execution context.
plugin dev.pnode.PNodePlugin The PNode plugin instance.

Inputs

NameTypeDescription
exec exec Incoming execution.
in0 any Input 0 — accessible as in0 in the script.
in1 any Input 1 — accessible as in1 in the script.
in2 any Input 2 — accessible as in2 in the script.
in3 any Input 3 — accessible as in3 in the script.

Outputs

NameTypeDescription
exec exec Continues after execution.
out0 any Output 0 — set as out0 in the script.
out1 any Output 1 — set as out1 in the script.
out2 any Output 2 — set as out2 in the script.
out3 any Output 3 — set as out3 in the script.

Groovy Examples

Hello World

Set out0 to a greeting.

out0 = "Hello, " + in0 + "!"

Get player UUID

Get the UUID of a player passed as in0.

out0 = in0?.getUniqueId()?.toString()

Format balance

Format a number as a currency string.

out0 = String.format("$%,.2f", (double) in0)

List all online player names

Collect all online player names into a list.

out0 = server.onlinePlayers.collect { it.name }

Check time of day

Return whether it is daytime in a world.

def world = server.getWorld("world")
def time = world?.time ?: 0
out0 = (time >= 0 && time < 12000) ? "day" : "night"

Send title with custom colours

Build and send a title using Adventure API.

import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor

def player = in0
def title = Component.text("Welcome!").color(NamedTextColor.GOLD)
def sub   = Component.text("Enjoy your stay").color(NamedTextColor.GRAY)
player?.showTitle(
  net.kyori.adventure.title.Title.title(title, sub)
)

Simple HTTP request (async)

Fetch data from an API asynchronously (enable async mode).

import groovy.json.JsonSlurper

def url = new URL("https://api.example.com/data")
def response = new JsonSlurper().parse(url)
logger.info("Got response: ${response}")

Math and conditions

Compute a tax amount and decide a category.

def income = (double) in0
def tax = income > 1000 ? income * 0.2 : income * 0.1
out0 = tax
out1 = tax > 200 ? "high" : "low"

Tips & Patterns

Null safety with ?.

Use the safe-navigation operator to avoid NullPointerExceptions.

// Safe — returns null if player is null instead of crashing
def name = in0?.name
out0 = name ?: "unknown"

String interpolation with GStrings

def player = in0
out0 = "Player ${player?.name} is at ${player?.location?.blockX}, ${player?.location?.blockY}"

Working with collections

// Filter online players with health below 5
def lowHealthPlayers = server.onlinePlayers
    .findAll { it.health < 5 }
    .collect { it.name }

out0 = lowHealthPlayers       // list of names
out1 = lowHealthPlayers.size() // count

Scheduling a delayed task

Use the Bukkit scheduler from inside a Custom Code node.

def targetPlayer = in0
plugin.server.scheduler.runTaskLater(plugin, {
    if (targetPlayer?.isOnline()) {
        targetPlayer.sendMessage("§aThis message arrived 5 seconds later!")
    }
} as Runnable, 100L) // 100 ticks = 5 seconds

Reading & writing YAML config values

// Read a value from PNode's config.yml
def webPort = plugin.config.getInt("web-server.port", 8080)
out0 = webPort

Using the execution context for cross-node data

// Store something for later nodes to read via Get Variable
ctx.setVariable("score", 42)

// Or read a previously set variable
def score = ctx.getVariable("score")
out0 = score

Parsing JSON (e.g. from an API)

import groovy.json.JsonSlurper

def jsonStr = in0 as String
def parsed  = new JsonSlurper().parseText(jsonStr)
out0 = parsed.someField
out1 = parsed.anotherField

Error handling

try {
    def result = someRiskyOperation(in0)
    out0 = result
    out1 = true  // success flag
} catch (Exception e) {
    logger.warning("Custom code error: ${e.message}")
    out0 = null
    out1 = false
}