Register event handlers, commands, and repeating tasks

Decorators

PyJavaBridge uses Python decorators to register event handlers, commands, and repeating tasks. Your decorated functions are discovered automatically when the script loads.


@event

@event
async def player_join(e):
    await e.player.send_message("Welcome!")

Register a function as an event handler. The function name determines the event type — it is converted from snake_case to PascalCase and Event is appended. For example, player_join becomes PlayerJoinEvent.

Signature

@event(*, once_per_tick=False, priority="NORMAL", throttle_ms=0)

Parameters

once_per_tick

When True, the handler is called at most once per server tick, even if the event fires multiple times. Useful for high-frequency events like player_move.

@event(once_per_tick=True)
async def player_move(e):
    # Called at most once per tick per event, not every micro-movement
    pass

priority

The Bukkit event priority. Determines the order in which handlers run. Options (lowest runs first):

Priority Description
LOWEST Runs first. Use for early cancellation.
LOW Runs before normal handlers.
NORMAL Default priority.
HIGH Runs after normal handlers.
HIGHEST Runs last before monitor.
MONITOR Read-only observation. Do not modify the event.
@event(priority="HIGH")
async def block_break(e):
    # This runs after NORMAL-priority handlers
    pass

throttle_ms

Minimum milliseconds between handler invocations. If the event fires more frequently, extra invocations are silently dropped.

@event(throttle_ms=500)
async def player_interact(e):
    # At most once every 500ms, prevents spam-clicking abuse
    pass

Bare decorator

You can use @event without parentheses for the default settings:

@event
async def player_quit(e):
    await server.broadcast(f"{e.player.name} left!")

@command

@command("Greet a player", permission="myplugin.greet")
async def greet(e):
    await e.player.send_message("Hello!")

Register a function as a script command. The command is automatically available as /functionname (or as specified via the name parameter). Command arguments are parsed from the function's additional parameters after event.

Signature

@command(description=None, *, name=None, permission=None, tab_complete=None)

Parameters

description

A human-readable description of the command. This is the first positional argument.

@command("Teleport to spawn")
async def spawn(e):
    ...

name

Override the command name. By default the function name is used.

@command("Teleport home", name="home")
async def teleport_home(e):
    # Registered as /home, not /teleport_home
    ...

permission

Permission node required to use the command. Players without this permission cannot execute it.

@command("Ban a player", permission="admin.ban")
async def ban(e, target: str):
    ...

tab_complete

Static tab completion suggestions per argument position (0-indexed). Use -1 as a wildcard for all positions without their own entry. For dynamic completions, see the @my_command.tab_complete decorator below.

@command("Set game mode", tab_complete={
    0: ["survival", "creative", "adventure", "spectator"]
})
async def gamemode(e, mode: str):
    ...
@command("Teleport to a place", tab_complete={
    0: ["spawn", "home", "shop", "arena"],
})
async def tp(e, place: str):
    ...

Wildcard example — suggest the same options for any argument position:

@command("Tag players", tab_complete={
    -1: ["vip", "admin", "builder", "mod"]
})
async def tag(e, *tags):
    ...

Dynamic tab completion

For dynamic completions that depend on server state (online players, entities, etc.), use the @my_command.tab_complete decorator:

@command("Teleport to a player")
async def tp(e, target: str):
    ...

@tp.tab_complete
async def tp_complete(sender, args):
    # args is the list of current arguments being typed
    players = await server.get_online_players()
    names = [p.name for p in players]
    # Filter by what the user has typed so far
    partial = args[-1].lower() if args else ""
    return [n for n in names if n.lower().startswith(partial)]

The tab completion handler receives:

It should return a list of suggestion strings. The handler can be async or synchronous.

@command("Give item to player")
async def give(e, player: str, item: str, amount: str = "1"):
    ...

@give.tab_complete
async def give_complete(sender, args):
    if len(args) <= 1:
        # First arg: player names
        players = await server.get_online_players()
        partial = args[0].lower() if args else ""
        return [p.name for p in players if p.name.lower().startswith(partial)]
    elif len(args) == 2:
        # Second arg: item types
        items = ["diamond", "iron_ingot", "gold_ingot", "emerald"]
        partial = args[1].lower()
        return [i for i in items if i.startswith(partial)]
    elif len(args) == 3:
        # Third arg: amounts
        return ["1", "16", "32", "64"]
    return []

Command arguments

Extra function parameters after the event become command arguments. They are parsed positionally from the player's input:

@command("Give items")
async def give(e, material: str, amount: str = "1"):
    # /give diamond 64
    # material = "diamond", amount = "64"
    await Item.give(e.player, material, int(amount))

All arguments are passed as strings. You handle type conversion yourself.


@task

@task(interval=20, delay=0)
async def tick_loop():
    ...

Register a repeating async task. The function is called repeatedly at the specified interval after an optional initial delay.

Signature

@task(*, interval=20, delay=0)

Parameters

interval

Ticks between invocations. Minecraft runs at 20 ticks per second, so interval=20 means once per second, interval=1 means every tick.

delay

Initial delay in ticks before the first invocation. Use this to wait for the server to fully start.

@task(interval=20*60, delay=20*5)
async def auto_save():
    # Runs every 60 seconds, starting 5 seconds after script load
    await server.execute("save-all")

Notes