diff --git a/README.md b/README.md
index f167e92..458622b 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
@@ -28,7 +28,10 @@ the same stable C ABI, so the server exposes each feature once and all plugins g
- **`java/`** - the `falcon-plugin-api` Maven artifact for Java plugins: bindings generated from `falcon_api.h`
at build time with the Foreign Function & Memory API, Java classes mirroring the C++ ones, and the loader the
server runs in its embedded Java virtual machine
-- **`examples/`** - complete plugins, built by CI on every supported platform
+- **`wasm/`** - the API for WebAssembly plugins: `falcon.wit`, which mirrors `falcon_api.h`, and a small C
+ helper (`wasm/c/falcon_wasm.h`) over the bindings `wit-bindgen` generates from it
+- **`examples/`** - complete plugins, built by CI on every supported platform except the Wasm one, which builds
+ with its own `build.sh`
The C++ headers only talk to the server through the C ABI, so a plugin built with any compiler runs on
any Falcon build of the same API major version.
diff --git a/examples/wasm/HelloWasm/.gitignore b/examples/wasm/HelloWasm/.gitignore
new file mode 100644
index 0000000..567609b
--- /dev/null
+++ b/examples/wasm/HelloWasm/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/examples/wasm/HelloWasm/HelloWasm.c b/examples/wasm/HelloWasm/HelloWasm.c
new file mode 100644
index 0000000..50bfd58
--- /dev/null
+++ b/examples/wasm/HelloWasm/HelloWasm.c
@@ -0,0 +1,19 @@
+#include "falcon_wasm.h"
+
+static void welcome(void) {
+ falcon_player player;
+ if (falcon_event_player(&player))
+ falcon_send(player, "Welcome! This message comes from WebAssembly.");
+}
+
+static bool hello(int argc, const char *const *argv) {
+ falcon_reply("Hello from WebAssembly!");
+ return true;
+}
+
+bool falcon_enable(void) {
+ falcon_on(FALCON_EVENT_PLAYER_JOIN, welcome);
+ falcon_command("hellowasm", "Answers from a WebAssembly plugin", hello);
+ falcon_log("HelloWasm is enabled");
+ return true;
+}
diff --git a/examples/wasm/HelloWasm/build.sh b/examples/wasm/HelloWasm/build.sh
new file mode 100755
index 0000000..dce0715
--- /dev/null
+++ b/examples/wasm/HelloWasm/build.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# Builds HelloWasm.wasm. Needs wit-bindgen, a clang with the wasm32 target and a WASI sysroot (wasi-sdk has
+# both): CLANG and WASI_SYSROOT point at them.
+set -euo pipefail
+
+cd "$(dirname "$0")"
+: "${CLANG:=clang}"
+: "${WASI_SYSROOT:?set WASI_SYSROOT to a WASI sysroot, such as wasi-sdk/share/wasi-sysroot}"
+WIT=../../../wasm
+
+mkdir -p build
+wit-bindgen c "$WIT" --out-dir build
+
+# The server instantiates the core module wit-bindgen targets, not a component: no `wasm-tools component new`.
+"$CLANG" --target=wasm32-wasip1 --sysroot="$WASI_SYSROOT" -mexec-model=reactor -O2 -Ibuild -I"$WIT/c" \
+ -o build/HelloWasm.wasm HelloWasm.c "$WIT/c/falcon_wasm.c" build/falcon_plugin.c \
+ build/falcon_plugin_component_type.o
+
+echo "Built build/HelloWasm.wasm"
diff --git a/examples/wasm/HelloWasm/plugin.json b/examples/wasm/HelloWasm/plugin.json
new file mode 100644
index 0000000..4eb2dd7
--- /dev/null
+++ b/examples/wasm/HelloWasm/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "HelloWasm",
+ "version": "1.0.0",
+ "runtime": "wasm",
+ "main": "HelloWasm.wasm",
+ "description": "Greets players and answers /hellowasm from WebAssembly",
+ "authors": ["Falcon"]
+}
diff --git a/wasm/c/falcon_wasm.c b/wasm/c/falcon_wasm.c
new file mode 100644
index 0000000..dd964ff
--- /dev/null
+++ b/wasm/c/falcon_wasm.c
@@ -0,0 +1,232 @@
+#include "falcon_wasm.h"
+
+#include
+#include
+
+struct subscription {
+ uint64_t id;
+ falcon_event_handler handler;
+};
+
+static struct subscription *subscriptions;
+static uint32_t subscriptionCount;
+static falcon_command_handler *commands;
+static uint32_t commandCount;
+static falcon_task_handler *tasks;
+static uint32_t taskCount;
+
+static bool grow(void **array, uint32_t count, size_t size) {
+ void *larger = realloc(*array, (count + 1) * size);
+ if (larger == NULL)
+ return false;
+ *array = larger;
+ return true;
+}
+
+static void logAt(falcon_plugin_types_log_level_t level, const char *message) {
+ falcon_plugin_string_t text;
+ falcon_plugin_string_set(&text, message);
+ falcon_plugin_server_log(level, &text);
+}
+
+void falcon_log(const char *message) {
+ logAt(FALCON_PLUGIN_TYPES_LOG_LEVEL_INFO, message);
+}
+
+void falcon_warn(const char *message) {
+ logAt(FALCON_PLUGIN_TYPES_LOG_LEVEL_WARNING, message);
+}
+
+void falcon_error(const char *message) {
+ logAt(FALCON_PLUGIN_TYPES_LOG_LEVEL_ERROR, message);
+}
+
+void falcon_broadcast(const char *message) {
+ falcon_plugin_string_t text;
+ falcon_plugin_string_set(&text, message);
+ falcon_plugin_server_broadcast_message(&text);
+}
+
+bool falcon_on(uint32_t event, falcon_event_handler handler) {
+ if (!grow((void **) &subscriptions, subscriptionCount, sizeof(*subscriptions)))
+ return false;
+
+ const uint64_t id = falcon_plugin_events_subscribe(event, FALCON_PLUGIN_TYPES_PRIORITY_NORMAL, false);
+ if (id == 0)
+ return false;
+
+ subscriptions[subscriptionCount].id = id;
+ subscriptions[subscriptionCount].handler = handler;
+ subscriptionCount++;
+ return true;
+}
+
+bool falcon_command(const char *name, const char *description, falcon_command_handler handler) {
+ if (!grow((void **) &commands, commandCount, sizeof(*commands)))
+ return false;
+
+ falcon_plugin_commands_command_descriptor_t descriptor;
+ falcon_plugin_string_set(&descriptor.name, name);
+ falcon_plugin_string_set(&descriptor.description, description);
+ falcon_plugin_string_set(&descriptor.usage, "");
+ descriptor.permission = FALCON_PLUGIN_TYPES_COMMAND_PERMISSION_ANY;
+ descriptor.handler = commandCount;
+ if (!falcon_plugin_commands_register_command(&descriptor))
+ return false;
+
+ commands[commandCount++] = handler;
+ return true;
+}
+
+void falcon_reply(const char *message) {
+ falcon_plugin_string_t text;
+ falcon_plugin_string_set(&text, message);
+ falcon_plugin_commands_sender_send_message(&text);
+}
+
+bool falcon_schedule(uint64_t delay, uint64_t period, falcon_task_handler handler) {
+ if (!grow((void **) &tasks, taskCount, sizeof(*tasks)))
+ return false;
+
+ if (falcon_plugin_scheduler_schedule_task(taskCount, delay, period) == 0)
+ return false;
+
+ tasks[taskCount++] = handler;
+ return true;
+}
+
+bool falcon_event_player(falcon_player *player) {
+ return falcon_plugin_event_event_player(player);
+}
+
+bool falcon_sender_player(falcon_player *player) {
+ return falcon_plugin_commands_sender_player(player);
+}
+
+void falcon_send(falcon_player player, const char *message) {
+ falcon_plugin_string_t text;
+ falcon_plugin_string_set(&text, message);
+ falcon_plugin_players_player_send_message(player, &text);
+}
+
+const char *falcon_player_name(falcon_player player, char *buffer, uint32_t size) {
+ falcon_plugin_string_t name;
+ falcon_plugin_players_player_name(player, &name);
+ const size_t length = size == 0 ? 0 : (name.len < size - 1 ? name.len : size - 1);
+ if (size > 0) {
+ memcpy(buffer, name.ptr, length);
+ buffer[length] = '\0';
+ }
+ falcon_plugin_string_free(&name);
+ return buffer;
+}
+
+__attribute__((weak)) void falcon_disable(void) {
+}
+
+void exports_falcon_plugin_plugin_on_load(void) {
+}
+
+bool exports_falcon_plugin_plugin_on_enable(void) {
+ return falcon_enable();
+}
+
+void exports_falcon_plugin_plugin_on_disable(void) {
+ falcon_disable();
+}
+
+void exports_falcon_plugin_plugin_on_event(uint64_t subscription) {
+ for (uint32_t index = 0; index < subscriptionCount; index++) {
+ if (subscriptions[index].id == subscription) {
+ subscriptions[index].handler();
+ return;
+ }
+ }
+}
+
+bool exports_falcon_plugin_plugin_on_command(uint32_t handler, falcon_plugin_list_string_t *arguments) {
+ // binding strings are not nul terminated: copied into c strings for argv
+ const size_t count = arguments->len;
+ const char **argv = calloc(count + 1, sizeof(*argv));
+ bool handled = false;
+ bool copied = argv != NULL;
+ for (size_t index = 0; copied && index < count; index++) {
+ char *argument = malloc(arguments->ptr[index].len + 1);
+ copied = argument != NULL;
+ if (copied) {
+ memcpy(argument, arguments->ptr[index].ptr, arguments->ptr[index].len);
+ argument[arguments->ptr[index].len] = '\0';
+ argv[index] = argument;
+ }
+ }
+
+ if (copied && handler < commandCount)
+ handled = commands[handler]((int) count, argv);
+
+ for (size_t index = 0; argv != NULL && index < count; index++)
+ free((char *) argv[index]);
+ free(argv);
+ falcon_plugin_list_string_free(arguments);
+ return handled;
+}
+
+void exports_falcon_plugin_plugin_on_task(uint64_t token) {
+ if (token < taskCount)
+ tasks[token]();
+}
+
+void exports_falcon_plugin_plugin_on_service(falcon_plugin_string_t *name, falcon_plugin_string_t *request,
+ falcon_plugin_string_t *ret) {
+ falcon_plugin_string_free(name);
+ falcon_plugin_string_free(request);
+ falcon_plugin_string_dup(ret, "");
+}
+
+bool exports_falcon_plugin_plugin_on_item_use(uint32_t handler, exports_falcon_plugin_plugin_player_t player,
+ exports_falcon_plugin_plugin_borrow_item_t item) {
+ (void) handler;
+ (void) player;
+ (void) item;
+ return false;
+}
+
+bool exports_falcon_plugin_plugin_on_item_use_on_block(uint32_t handler, exports_falcon_plugin_plugin_player_t player,
+ exports_falcon_plugin_plugin_borrow_item_t item,
+ exports_falcon_plugin_plugin_block_pos_t *position,
+ uint32_t face) {
+ (void) handler;
+ (void) player;
+ (void) item;
+ (void) position;
+ (void) face;
+ return false;
+}
+
+bool exports_falcon_plugin_plugin_on_block_interact(uint32_t handler, exports_falcon_plugin_plugin_player_t player,
+ exports_falcon_plugin_plugin_block_pos_t *position, uint32_t face) {
+ (void) handler;
+ (void) player;
+ (void) position;
+ (void) face;
+ return false;
+}
+
+void exports_falcon_plugin_plugin_on_block_break(uint32_t handler, exports_falcon_plugin_plugin_player_t player,
+ exports_falcon_plugin_plugin_block_pos_t *position) {
+ (void) handler;
+ (void) player;
+ (void) position;
+}
+
+void exports_falcon_plugin_plugin_on_entity_tick(uint32_t handler, exports_falcon_plugin_plugin_entity_t entity) {
+ (void) handler;
+ (void) entity;
+}
+
+bool exports_falcon_plugin_plugin_on_entity_interact(uint32_t handler, exports_falcon_plugin_plugin_entity_t entity,
+ exports_falcon_plugin_plugin_player_t player) {
+ (void) handler;
+ (void) entity;
+ (void) player;
+ return false;
+}
diff --git a/wasm/c/falcon_wasm.h b/wasm/c/falcon_wasm.h
new file mode 100644
index 0000000..85439c2
--- /dev/null
+++ b/wasm/c/falcon_wasm.h
@@ -0,0 +1,130 @@
+#ifndef FALCON_WASM_H
+#define FALCON_WASM_H
+
+/* plain callbacks over the wit-bindgen bindings; falcon_plugin.h stays usable for everything not wrapped */
+
+#include "falcon_plugin.h"
+
+#include
+#include
+
+typedef uint64_t falcon_player;
+
+typedef void (*falcon_event_handler)(void);
+
+typedef bool (*falcon_command_handler)(int argc, const char *const *argv);
+
+typedef void (*falcon_task_handler)(void);
+
+/* implemented by the plugin: false refuses to enable */
+bool falcon_enable(void);
+
+/* optional */
+void falcon_disable(void);
+
+void falcon_log(const char *message);
+
+void falcon_warn(const char *message);
+
+void falcon_error(const char *message);
+
+void falcon_broadcast(const char *message);
+
+/* type: a FALCON_EVENT_* value; false when refused */
+bool falcon_on(uint32_t event, falcon_event_handler handler);
+
+/* false when the name is taken */
+bool falcon_command(const char *name, const char *description, falcon_command_handler handler);
+
+void falcon_reply(const char *message);
+
+/* period 0: runs once after delay ticks */
+bool falcon_schedule(uint64_t delay, uint64_t period, falcon_task_handler handler);
+
+bool falcon_event_player(falcon_player *player);
+
+bool falcon_sender_player(falcon_player *player);
+
+void falcon_send(falcon_player player, const char *message);
+
+/* truncated to fit */
+const char *falcon_player_name(falcon_player player, char *buffer, uint32_t size);
+
+#define FALCON_EVENT_PLAYER_JOIN 1u
+#define FALCON_EVENT_PLAYER_QUIT 2u
+#define FALCON_EVENT_PLAYER_CHAT 3u
+#define FALCON_EVENT_BLOCK_BREAK 4u
+#define FALCON_EVENT_BLOCK_PLACE 5u
+#define FALCON_EVENT_PLAYER_INTERACT_BLOCK 6u
+#define FALCON_EVENT_ENTITY_DAMAGE 7u
+#define FALCON_EVENT_ENTITY_DEATH 8u
+#define FALCON_EVENT_PLAYER_DEATH 9u
+#define FALCON_EVENT_PLAYER_RESPAWN 10u
+#define FALCON_EVENT_PLAYER_MOVE 11u
+#define FALCON_EVENT_PLAYER_DROP_ITEM 12u
+#define FALCON_EVENT_PLAYER_PICKUP_ITEM 13u
+#define FALCON_EVENT_DATA_PACKET_RECEIVE 14u
+#define FALCON_EVENT_DATA_PACKET_SEND 15u
+#define FALCON_EVENT_PLAYER_INTERACT_ENTITY 16u
+#define FALCON_EVENT_INVENTORY_OPEN 17u
+#define FALCON_EVENT_INVENTORY_CLOSE 18u
+#define FALCON_EVENT_INVENTORY_TRANSACTION 19u
+#define FALCON_EVENT_CRAFT_ITEM 20u
+#define FALCON_EVENT_PLAYER_COMMAND 21u
+#define FALCON_EVENT_SERVER_COMMAND 22u
+#define FALCON_EVENT_SERVER_TICK 23u
+#define FALCON_EVENT_PLAYER_GAME_MODE_CHANGE 24u
+#define FALCON_EVENT_PLAYER_CHANGE_DIMENSION 25u
+#define FALCON_EVENT_PROJECTILE_HIT 26u
+#define FALCON_EVENT_EXPLOSION 27u
+#define FALCON_EVENT_FIRE_SPREAD 28u
+#define FALCON_EVENT_BLOCK_BURN 29u
+#define FALCON_EVENT_CUSTOM 30u
+#define FALCON_EVENT_PLAYER_PRE_LOGIN 31u
+#define FALCON_EVENT_PLAYER_TOGGLE_SNEAK 32u
+#define FALCON_EVENT_PLAYER_TOGGLE_SPRINT 33u
+#define FALCON_EVENT_PLAYER_TOGGLE_FLIGHT 34u
+#define FALCON_EVENT_PLAYER_ITEM_HELD 35u
+#define FALCON_EVENT_PLAYER_ITEM_CONSUME 36u
+#define FALCON_EVENT_PLAYER_BED_ENTER 37u
+#define FALCON_EVENT_PLAYER_BED_LEAVE 38u
+#define FALCON_EVENT_PLAYER_JUMP 39u
+#define FALCON_EVENT_PLAYER_FOOD_CHANGE 40u
+#define FALCON_EVENT_PLAYER_EXPERIENCE_CHANGE 41u
+#define FALCON_EVENT_ENTITY_SPAWN 42u
+#define FALCON_EVENT_ENTITY_TRANSFORM 43u
+#define FALCON_EVENT_ENTITY_TARGET 44u
+#define FALCON_EVENT_PROJECTILE_LAUNCH 45u
+#define FALCON_EVENT_BLOCK_GROW 46u
+#define FALCON_EVENT_BLOCK_SPREAD 47u
+#define FALCON_EVENT_BLOCK_FORM 48u
+#define FALCON_EVENT_BLOCK_FADE 49u
+#define FALCON_EVENT_LIQUID_FLOW 50u
+#define FALCON_EVENT_PISTON_EXTEND 51u
+#define FALCON_EVENT_PISTON_RETRACT 52u
+#define FALCON_EVENT_REDSTONE_CHANGE 53u
+#define FALCON_EVENT_SIGN_CHANGE 54u
+#define FALCON_EVENT_LEAVES_DECAY 55u
+#define FALCON_EVENT_WEATHER_CHANGE 56u
+#define FALCON_EVENT_THUNDER_CHANGE 57u
+#define FALCON_EVENT_CHUNK_LOAD 58u
+#define FALCON_EVENT_CHUNK_UNLOAD 59u
+#define FALCON_EVENT_FURNACE_SMELT 60u
+#define FALCON_EVENT_FURNACE_BURN 61u
+#define FALCON_EVENT_ITEM_ENCHANT 62u
+#define FALCON_EVENT_ITEM_DAMAGE 63u
+#define FALCON_EVENT_ITEM_BREAK 64u
+#define FALCON_EVENT_WORLD_LOAD 65u
+#define FALCON_EVENT_WORLD_UNLOAD 66u
+#define FALCON_EVENT_PLAYER_CHANGE_WORLD 67u
+#define FALCON_EVENT_CONNECTION_OPEN 68u
+#define FALCON_EVENT_PLAYER_FORM_RESPONSE 69u
+#define FALCON_GAME_MODE_SURVIVAL 0u
+#define FALCON_GAME_MODE_CREATIVE 1u
+#define FALCON_GAME_MODE_ADVENTURE 2u
+#define FALCON_GAME_MODE_SPECTATOR 6u
+#define FALCON_DIMENSION_OVERWORLD 0u
+#define FALCON_DIMENSION_NETHER 1u
+#define FALCON_DIMENSION_THE_END 2u
+
+#endif
diff --git a/wasm/falcon.wit b/wasm/falcon.wit
new file mode 100644
index 0000000..dfe833d
--- /dev/null
+++ b/wasm/falcon.wit
@@ -0,0 +1,421 @@
+/// The Falcon plugin API for WebAssembly plugins.
+///
+/// Every imported function mirrors one entry of the C API table in `falcon_api.h` (API 1.3.3) and calls it,
+/// so a Wasm plugin reaches exactly what a native plugin reaches. The only C entry without a counterpart is
+/// `runAsync`: a plugin instance is not thread safe, so it cannot run on the scheduler's worker thread.
+///
+/// Handles are never pointers. Players and entities are their runtime id and are looked up on every call, so
+/// a stale id is simply not found. Levels and items are host-side handles that are checked on every call.
+/// Events and command senders have no handle: `event` and the sender functions of `commands` act on the
+/// event or command being handled and return default values outside a handler.
+package falcon:plugin@1.3.3;
+
+interface types {
+ /// The runtime id of a player. A player is also an entity, so the same id works with `entities`.
+ type player = u64;
+
+ /// The runtime id of an entity.
+ type entity = u64;
+
+ /// One of the `FALCON_DIMENSION_*` values.
+ type dimension = u32;
+
+ /// One of the `FALCON_GAME_MODE_*` values.
+ type game-mode = u32;
+
+ /// One of the `FALCON_EVENT_*` values.
+ type event-kind = u32;
+
+ record vec3 {
+ x: f64,
+ y: f64,
+ z: f64,
+ }
+
+ record block-pos {
+ x: s32,
+ y: s32,
+ z: s32,
+ }
+
+ /// `FALCON_LOG_*`, in order.
+ enum log-level {
+ info,
+ warning,
+ error,
+ }
+
+ /// `FALCON_PRIORITY_*`, in order.
+ enum priority {
+ lowest,
+ low,
+ normal,
+ high,
+ highest,
+ monitor,
+ }
+
+ /// `FALCON_PERMISSION_DEFAULT_FALSE`, `_TRUE` and `_OPERATOR`, in order.
+ enum permission-default {
+ deny,
+ allow,
+ operator,
+ }
+
+ /// `FALCON_PERMISSION_ANY` and `FALCON_PERMISSION_OPERATOR`, in order.
+ enum command-permission {
+ any,
+ operator,
+ }
+
+ /// `FALCON_ARMOR_*`, in order.
+ enum armor-slot {
+ head,
+ chest,
+ legs,
+ feet,
+ }
+
+ /// A level of a loaded world. A level whose world was unloaded behaves like a missing one.
+ resource level;
+
+ /// An item stack. Items returned by `item-create`, `item-copy` and the inventory getters are copies owned
+ /// by the plugin. Items returned by `event-item` and `event-result`, and the item passed to a custom item
+ /// handler, are the server's own stack and stop working when the handler returns.
+ resource item;
+}
+
+interface server {
+ use types.{player, log-level};
+
+ server-version: func() -> string;
+ log: func(level: log-level, message: string);
+ plugin-name: func() -> string;
+ plugin-data-folder: func() -> string;
+ online-player-count: func() -> u32;
+ online-player: func(index: u32) -> option;
+ find-player: func(name: string) -> option;
+ broadcast-message: func(message: string);
+}
+
+interface players {
+ use types.{player, entity, game-mode};
+
+ player-name: func(player: player) -> string;
+ player-is-operator: func(player: player) -> bool;
+ player-send-message: func(player: player, message: string);
+ player-kick: func(player: player, reason: string);
+ player-entity: func(player: player) -> option;
+ entity-player: func(entity: entity) -> option;
+ player-game-mode: func(player: player) -> game-mode;
+ player-set-game-mode: func(player: player, game-mode: game-mode);
+ player-xuid: func(player: player) -> string;
+ player-uuid: func(player: player) -> string;
+ player-address: func(player: player) -> string;
+ player-send-title: func(player: player, title: string, subtitle: string);
+ player-send-action-bar: func(player: player, message: string);
+ player-food: func(player: player) -> f32;
+ player-set-food: func(player: player, food: f32);
+ player-xp-level: func(player: player) -> s32;
+ player-set-xp-level: func(player: player, level: s32);
+ player-set-operator: func(player: player, operator: bool);
+ player-send-form: func(player: player, form-json: string) -> u32;
+ player-transfer: func(player: player, address: string, port: u32) -> bool;
+ player-protocol-version: func(player: player) -> s32;
+ player-send-packet: func(player: player, packet-id: u32, data: list) -> bool;
+}
+
+interface entities {
+ use types.{entity, level, vec3};
+
+ entity-type: func(entity: entity) -> string;
+ entity-runtime-id: func(entity: entity) -> u64;
+ entity-level: func(entity: entity) -> option;
+ entity-position: func(entity: entity) -> vec3;
+ entity-rotation: func(entity: entity) -> vec3;
+ entity-teleport: func(entity: entity, level: option>, position: vec3);
+ entity-motion: func(entity: entity) -> vec3;
+ entity-set-motion: func(entity: entity, motion: vec3);
+ entity-health: func(entity: entity) -> f32;
+ entity-max-health: func(entity: entity) -> f32;
+ entity-set-health: func(entity: entity, health: f32);
+ entity-is-alive: func(entity: entity) -> bool;
+ entity-damage: func(entity: entity, amount: f32, cause: option, attacker: option) -> bool;
+ entity-kill: func(entity: entity);
+ entity-remove: func(entity: entity);
+ entity-name-tag: func(entity: entity) -> string;
+ entity-set-name-tag: func(entity: entity, name-tag: string);
+ entity-is-on-fire: func(entity: entity) -> bool;
+ entity-set-on-fire: func(entity: entity, ticks: u32);
+ entity-is-invisible: func(entity: entity) -> bool;
+ entity-set-invisible: func(entity: entity, invisible: bool);
+ level-entity-count: func(level: borrow) -> u32;
+ level-entity: func(level: borrow, index: u32) -> option;
+}
+
+interface levels {
+ use types.{entity, dimension, level, item, vec3, block-pos};
+
+ server-level: func(dimension: dimension) -> option;
+ level-dimension: func(level: borrow) -> dimension;
+ level-name: func(level: borrow) -> string;
+ level-get-block: func(level: borrow, position: block-pos) -> string;
+ level-get-block-states: func(level: borrow, position: block-pos) -> string;
+ level-set-block: func(level: borrow, position: block-pos, name: string, states-json: option) -> bool;
+ level-break-block: func(level: borrow, position: block-pos, drop-items: bool) -> bool;
+ level-is-chunk-loaded: func(level: borrow, chunk-x: s32, chunk-z: s32) -> bool;
+ level-highest-block-y: func(level: borrow, x: s32, z: s32) -> s32;
+ level-time: func(level: borrow) -> s64;
+ level-set-time: func(level: borrow, time: s64);
+ level-is-raining: func(level: borrow) -> bool;
+ level-set-raining: func(level: borrow, raining: bool);
+ level-is-thundering: func(level: borrow) -> bool;
+ level-set-thundering: func(level: borrow, thundering: bool);
+ level-spawn-position: func(level: borrow) -> vec3;
+ level-spawn-entity: func(level: borrow, identifier: string, position: vec3) -> option;
+ level-drop-item: func(level: borrow, position: vec3, item: borrow- );
+ level-strike-lightning: func(level: borrow, position: vec3);
+ level-create-explosion: func(level: borrow, position: vec3, power: f32, break-blocks: bool);
+ server-world-count: func() -> u32;
+ server-world-name: func(index: u32) -> option;
+ server-default-world-name: func() -> string;
+ world-level: func(name: string, dimension: dimension) -> option;
+ server-load-world: func(name: string, create: bool, seed: option) -> bool;
+ server-unload-world: func(name: string) -> bool;
+}
+
+interface items {
+ use types.{player, item, armor-slot};
+
+ item-create: func(identifier: string, count: u32) -> option
- ;
+ item-copy: func(item: borrow
- ) -> option
- ;
+ item-is-empty: func(item: borrow
- ) -> bool;
+ item-identifier: func(item: borrow
- ) -> string;
+ item-count: func(item: borrow
- ) -> u32;
+ item-set-count: func(item: borrow
- , count: u32);
+ item-damage: func(item: borrow
- ) -> s32;
+ item-set-damage: func(item: borrow
- , damage: s32);
+ item-custom-name: func(item: borrow
- ) -> option;
+ item-set-custom-name: func(item: borrow
- , name: option);
+ item-lore-count: func(item: borrow
- ) -> u32;
+ item-lore: func(item: borrow
- , index: u32) -> option;
+ item-set-lore: func(item: borrow
- , lines: list);
+ item-enchantment-level: func(item: borrow
- , enchantment: u32) -> s32;
+ item-set-enchantment-level: func(item: borrow
- , enchantment: u32, level: s32);
+ player-inventory-size: func(player: player) -> u32;
+ player-inventory-item: func(player: player, slot: u32) -> option
- ;
+ player-set-inventory-item: func(player: player, slot: u32, item: option>);
+ player-give-item: func(player: player, item: borrow
- ) -> bool;
+ player-selected-slot: func(player: player) -> u32;
+ player-set-selected-slot: func(player: player, slot: u32);
+ player-armor-item: func(player: player, slot: armor-slot) -> option
- ;
+ player-set-armor-item: func(player: player, slot: armor-slot, item: option>);
+ player-offhand-item: func(player: player) -> option
- ;
+ player-set-offhand-item: func(player: player, item: option>);
+ player-clear-inventory: func(player: player);
+}
+
+interface events {
+ use types.{event-kind, priority};
+
+ record custom-event-result {
+ data: option,
+ cancelled: bool,
+ }
+
+ /// Returns the subscription id, or 0 when the subscription was refused. `on-event` receives this id.
+ subscribe: func(kind: event-kind, priority: priority, ignore-cancelled: bool) -> u64;
+ subscribe-custom-event: func(name: string, priority: priority, ignore-cancelled: bool) -> u64;
+ /// Only removes subscriptions of this plugin.
+ unsubscribe: func(subscription: u64);
+ fire-custom-event: func(name: string, data: string, cancellable: bool) -> custom-event-result;
+}
+
+/// The event being handled by `on-event`.
+interface event {
+ use types.{player, entity, level, item, event-kind, game-mode, dimension, vec3, block-pos};
+
+ event-type: func() -> event-kind;
+ event-is-cancellable: func() -> bool;
+ event-is-cancelled: func() -> bool;
+ event-set-cancelled: func(cancelled: bool);
+ event-player: func() -> option;
+ event-message: func() -> option;
+ event-set-message: func(message: string);
+ event-entity: func() -> option;
+ event-attacker: func() -> option;
+ event-block-position: func() -> block-pos;
+ event-block-face: func() -> u32;
+ event-block-name: func() -> string;
+ event-amount: func() -> f64;
+ event-set-amount: func(amount: f64);
+ event-cause: func() -> string;
+ event-from: func() -> vec3;
+ event-to: func() -> vec3;
+ event-set-to: func(position: vec3);
+ event-item: func() -> option
- ;
+ event-packet-id: func() -> u32;
+ event-packet-data: func() -> list;
+ event-set-packet-data: func(data: list);
+ event-level: func() -> option;
+ event-target: func() -> option;
+ event-position: func() -> vec3;
+ event-block-count: func() -> u32;
+ event-block-at: func(index: u32) -> block-pos;
+ event-set-blocks: func(positions: list);
+ event-game-mode: func() -> game-mode;
+ event-previous-game-mode: func() -> game-mode;
+ event-dimension: func() -> dimension;
+ event-previous-dimension: func() -> dimension;
+ event-tick: func() -> u64;
+ event-source-container: func() -> string;
+ event-source-slot: func() -> s32;
+ event-destination-container: func() -> string;
+ event-destination-slot: func() -> s32;
+ event-name: func() -> string;
+ event-data: func() -> string;
+ event-set-data: func(data: string);
+ event-state: func() -> bool;
+ event-previous-amount: func() -> f64;
+ event-previous-block-name: func() -> string;
+ event-chunk-x: func() -> s32;
+ event-chunk-z: func() -> s32;
+ event-result: func() -> option
- ;
+ event-set-result: func(item: borrow
- );
+ event-world-name: func() -> string;
+ event-previous-world-name: func() -> string;
+ event-address: func() -> string;
+ event-transport: func() -> string;
+ event-client-guid: func() -> u64;
+ event-mtu-size: func() -> u32;
+ event-client-data: func() -> string;
+ event-client-data-field: func(key: string) -> string;
+ event-form-id: func() -> u32;
+ event-form-response: func() -> string;
+}
+
+/// Command registration, and the sender of the command being handled by `on-command`.
+interface commands {
+ use types.{player, command-permission};
+
+ record command-descriptor {
+ name: string,
+ description: string,
+ usage: string,
+ permission: command-permission,
+ /// Passed back to `on-command`.
+ handler: u32,
+ }
+
+ register-command: func(descriptor: command-descriptor) -> bool;
+ sender-name: func() -> string;
+ sender-player: func() -> option;
+ sender-send-message: func(message: string);
+ sender-has-permission: func(node: string) -> bool;
+}
+
+interface scheduler {
+ /// `token` is passed back to `on-task`. Returns the task id, or 0 when the task was refused.
+ schedule-task: func(token: u64, delay-ticks: u64, period-ticks: u64) -> u64;
+ /// Only cancels tasks of this plugin.
+ cancel-task: func(task: u64);
+}
+
+interface permissions {
+ use types.{player, permission-default};
+
+ register-permission: func(node: string, default-value: permission-default) -> bool;
+ player-has-permission: func(player: player, node: string) -> bool;
+ player-set-permission: func(player: player, node: string, value: bool);
+ player-unset-permission: func(player: player, node: string);
+}
+
+interface services {
+ /// `on-service` is called with `name` when a plugin calls the service.
+ register-service: func(name: string) -> bool;
+ unregister-service: func(name: string);
+ has-service: func(name: string) -> bool;
+ service-provider: func(name: string) -> option;
+ call-service: func(name: string, request: string) -> option;
+}
+
+/// Custom content. A callback left as `none` is not registered, like a null handler in the C API; the value of
+/// a callback that is set is passed back to the matching `on-*` function.
+interface content {
+ record custom-item {
+ identifier: string,
+ display-name: string,
+ icon: string,
+ creative-category: string,
+ max-stack-size: u32,
+ max-durability: u32,
+ hand-equipped: bool,
+ on-use: option,
+ on-use-on-block: option,
+ }
+
+ record custom-block {
+ identifier: string,
+ display-name: string,
+ texture: string,
+ creative-category: string,
+ destroy-time: f32,
+ explosion-resistance: f32,
+ light-emission: u32,
+ friction: f32,
+ drop: option,
+ on-interact: option,
+ on-break: option,
+ }
+
+ record custom-entity {
+ identifier: string,
+ width: f32,
+ height: f32,
+ max-health: f32,
+ summonable: bool,
+ on-tick: option,
+ on-interact: option,
+ }
+
+ register-custom-item: func(descriptor: custom-item) -> bool;
+ register-custom-block: func(descriptor: custom-block) -> bool;
+ register-custom-entity: func(descriptor: custom-entity) -> bool;
+}
+
+/// The functions the server calls: `FalconPluginCallbacks` and the C handler callbacks.
+interface plugin {
+ use types.{player, entity, item, block-pos};
+
+ on-load: func();
+ on-enable: func() -> bool;
+ on-disable: func();
+ on-event: func(subscription: u64);
+ on-command: func(handler: u32, arguments: list) -> bool;
+ on-task: func(token: u64);
+ on-service: func(name: string, request: string) -> string;
+ on-item-use: func(handler: u32, player: player, item: borrow
- ) -> bool;
+ on-item-use-on-block: func(handler: u32, player: player, item: borrow
- , position: block-pos, face: u32) -> bool;
+ on-block-interact: func(handler: u32, player: player, position: block-pos, face: u32) -> bool;
+ on-block-break: func(handler: u32, player: player, position: block-pos);
+ on-entity-tick: func(handler: u32, entity: entity);
+ on-entity-interact: func(handler: u32, entity: entity, player: player) -> bool;
+}
+
+world falcon-plugin {
+ import types;
+ import server;
+ import players;
+ import entities;
+ import levels;
+ import items;
+ import events;
+ import event;
+ import commands;
+ import scheduler;
+ import permissions;
+ import services;
+ import content;
+
+ export plugin;
+}