From 1a4b1fdeb06ea5f95e78414c46541064c98e9c63 Mon Sep 17 00:00:00 2001 From: NaySurGithub <96527285+NaySurGithub@users.noreply.github.com> Date: Sat, 14 Nov 2026 21:30:58 +0100 Subject: [PATCH 1/2] feat: add entity nbt writing and player skin changes to the plugin api --- README.md | 6 +++--- java/src/main/java/falcon/api/Entity.java | 12 ++++++++++++ java/src/main/java/falcon/api/Player.java | 13 +++++++++++++ native/include/falcon/Entity.hpp | 8 ++++++++ native/include/falcon/Player.hpp | 9 +++++++++ native/include/falcon/falcon_api.h | 4 ++++ wasm/falcon.wit | 4 ++++ 7 files changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 50a4030..b062b08 100644 --- a/README.md +++ b/README.md @@ -197,8 +197,8 @@ if (std::optional held = inventory.item(inventory.selectedSlot())) ``` Java plugins reach the same store through `plugin.data()`. `Item::nbt` and `Item::setNbt` read and replace an -item's whole tag as little-endian NBT, the game's own format, and `Entity::nbt` returns what the server would save -for an entity. +item's whole tag as little-endian NBT, the game's own format. `Entity::nbt` returns what the server would save +for an entity, and `Entity::setNbt` loads a mob or any other non-player entity back from such data. ### Typed commands, running commands and recipes @@ -249,7 +249,7 @@ falcon::Server::setDifficulty(falcon::Difficulty::Hard); ### Entities and players Mobs expose their equipment, riding, tags and attack target; players their spawn point, language, platform, skin, -camera and item cooldowns: +camera and item cooldowns. `Player::setSkin` gives a player a classic skin of at most 128 by 128 pixels: ```cpp zombie.setEquipment(falcon::EquipmentSlot::Head, *falcon::Item::create("minecraft:diamond_helmet")); diff --git a/java/src/main/java/falcon/api/Entity.java b/java/src/main/java/falcon/api/Entity.java index fe5335c..a9e59ee 100644 --- a/java/src/main/java/falcon/api/Entity.java +++ b/java/src/main/java/falcon/api/Entity.java @@ -241,6 +241,18 @@ public byte[] nbt() { } } + /** + * Loads the entity from little-endian NBT, as the server does from a save, and shows the result to the players + * who see it. Returns false for data that is not a compound, and for players. + */ + public boolean setNbt(byte[] data) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment bytes = data.length == 0 ? MemorySegment.NULL : arena.allocateFrom(ValueLayout.JAVA_BYTE, + data); + return api().entitySetNbt(mHandle, bytes, data.length) != 0; + } + } + /** * What a mob wears or holds; empty for an empty slot or an entity without equipment, players included. */ diff --git a/java/src/main/java/falcon/api/Player.java b/java/src/main/java/falcon/api/Player.java index a564325..018d73f 100644 --- a/java/src/main/java/falcon/api/Player.java +++ b/java/src/main/java/falcon/api/Player.java @@ -200,6 +200,19 @@ public SkinImage skinImage() { } } + /** + * Gives the player a classic skin and shows it to everyone. The image is RGBA, four bytes a pixel, at most 128 + * by 128. Returns false when the image does not have the size it announces. + */ + public boolean setSkin(String skinId, SkinImage image) { + try (Arena arena = Arena.ofConfined()) { + byte[] rgba = image.rgba(); + MemorySegment bytes = rgba.length == 0 ? MemorySegment.NULL : arena.allocateFrom(ValueLayout.JAVA_BYTE, + rgba); + return api().playerSetSkin(mHandle, Interop.text(arena, skinId), bytes, image.width(), image.height()) != 0; + } + } + /** * Switches the camera to a preset such as minecraft:free, easing over easeTime seconds when above 0. */ diff --git a/native/include/falcon/Entity.hpp b/native/include/falcon/Entity.hpp index ac30444..eb84386 100644 --- a/native/include/falcon/Entity.hpp +++ b/native/include/falcon/Entity.hpp @@ -231,6 +231,14 @@ namespace falcon { return std::vector(data, data + length); } + /** + * Loads the entity from little-endian NBT, as the server does from a save, and shows the result to the + * players who see it. Returns false for data that is not a compound, and for players. + */ + bool setNbt(const std::vector &data) const { + return detail::api().entitySetNbt(mHandle, data.data(), static_cast(data.size())) != 0; + } + /** What a mob wears or holds; nothing for an empty slot or an entity without equipment, players included. */ std::optional equipment(EquipmentSlot slot) const { const FalconEquipmentSlot raw = static_cast(slot); diff --git a/native/include/falcon/Player.hpp b/native/include/falcon/Player.hpp index e0c2ac6..fd72d18 100644 --- a/native/include/falcon/Player.hpp +++ b/native/include/falcon/Player.hpp @@ -210,6 +210,15 @@ namespace falcon { return image; } + /** + * Gives the player a classic skin and shows it to everyone. The image is RGBA, four bytes a pixel, at most + * 128 by 128. Returns false when the image does not have the size it announces. + */ + bool setSkin(const std::string &skinId, const SkinImage &image) const { + return detail::api().playerSetSkin(mHandle, skinId.c_str(), image.rgba.data(), image.width, + image.height) != 0; + } + /** Switches the camera to a preset such as minecraft:free, easing over easeTime seconds when above 0. */ bool setCamera(const std::string &preset, float easeTime = 0.0f, CameraEase ease = CameraEase::Linear) const { return detail::api().playerSetCamera(mHandle, preset.c_str(), nullptr, nullptr, easeTime, diff --git a/native/include/falcon/falcon_api.h b/native/include/falcon/falcon_api.h index 016c554..0f830d8 100644 --- a/native/include/falcon/falcon_api.h +++ b/native/include/falcon/falcon_api.h @@ -790,6 +790,10 @@ typedef struct FalconServerApi { int (*serverPluginEnabled)(const char *name); int (*eventEffect)(FalconEvent *event, FalconEffect *out); + + int (*entitySetNbt)(FalconEntity *entity, const uint8_t *data, uint32_t length); + int (*playerSetSkin)(FalconPlayer *player, const char *skinId, const uint8_t *rgba, uint32_t width, + uint32_t height); } FalconServerApi; typedef int (*FalconPluginEntry)(const FalconServerApi *api, FalconPlugin *plugin, FalconPluginCallbacks *callbacks); diff --git a/wasm/falcon.wit b/wasm/falcon.wit index 8640cf1..f87407d 100644 --- a/wasm/falcon.wit +++ b/wasm/falcon.wit @@ -215,6 +215,8 @@ interface players { player-device-os: func(player: player) -> s32; player-skin-id: func(player: player) -> string; player-skin-image: func(player: player) -> skin-image; + /// A classic skin, at most 128 by 128, shown to everyone. + player-set-skin: func(player: player, skin-id: string, image: skin-image) -> bool; /// `ease`: one of the `FALCON_CAMERA_EASE_*` values, used when `ease-time` is above 0. player-set-camera: func(player: player, preset: string, position: option, rotation: option, ease-time: f32, ease: u32) -> bool; @@ -270,6 +272,8 @@ interface entities { entity-data-key: func(entity: entity, index: u32) -> option; /// What the server would save for the entity, as little-endian NBT. entity-nbt: func(entity: entity) -> list; + /// Loads the entity from little-endian NBT and shows the result to the players who see it; not for players. + entity-set-nbt: func(entity: entity, data: list) -> bool; /// `slot`: one of the `FALCON_EQUIPMENT_*` values; mobs only, `none` for an empty slot. entity-equipment: func(entity: entity, slot: u32) -> option; entity-set-equipment: func(entity: entity, slot: u32, item: option>) -> bool; From e82300761d950947548b764c08c3f635da37c838 Mon Sep 17 00:00:00 2001 From: NaySurGithub <96527285+NaySurGithub@users.noreply.github.com> Date: Sat, 14 Nov 2026 21:32:09 +0100 Subject: [PATCH 2/2] feat: pass the skin image length to playerSetSkin --- java/src/main/java/falcon/api/Player.java | 3 ++- native/include/falcon/Player.hpp | 3 ++- native/include/falcon/falcon_api.h | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/java/src/main/java/falcon/api/Player.java b/java/src/main/java/falcon/api/Player.java index 018d73f..74b7029 100644 --- a/java/src/main/java/falcon/api/Player.java +++ b/java/src/main/java/falcon/api/Player.java @@ -209,7 +209,8 @@ public boolean setSkin(String skinId, SkinImage image) { byte[] rgba = image.rgba(); MemorySegment bytes = rgba.length == 0 ? MemorySegment.NULL : arena.allocateFrom(ValueLayout.JAVA_BYTE, rgba); - return api().playerSetSkin(mHandle, Interop.text(arena, skinId), bytes, image.width(), image.height()) != 0; + return api().playerSetSkin(mHandle, Interop.text(arena, skinId), bytes, rgba.length, image.width(), + image.height()) != 0; } } diff --git a/native/include/falcon/Player.hpp b/native/include/falcon/Player.hpp index fd72d18..014c78f 100644 --- a/native/include/falcon/Player.hpp +++ b/native/include/falcon/Player.hpp @@ -215,7 +215,8 @@ namespace falcon { * 128 by 128. Returns false when the image does not have the size it announces. */ bool setSkin(const std::string &skinId, const SkinImage &image) const { - return detail::api().playerSetSkin(mHandle, skinId.c_str(), image.rgba.data(), image.width, + return detail::api().playerSetSkin(mHandle, skinId.c_str(), image.rgba.data(), + static_cast(image.rgba.size()), image.width, image.height) != 0; } diff --git a/native/include/falcon/falcon_api.h b/native/include/falcon/falcon_api.h index 0f830d8..d95f3c0 100644 --- a/native/include/falcon/falcon_api.h +++ b/native/include/falcon/falcon_api.h @@ -792,8 +792,8 @@ typedef struct FalconServerApi { int (*eventEffect)(FalconEvent *event, FalconEffect *out); int (*entitySetNbt)(FalconEntity *entity, const uint8_t *data, uint32_t length); - int (*playerSetSkin)(FalconPlayer *player, const char *skinId, const uint8_t *rgba, uint32_t width, - uint32_t height); + int (*playerSetSkin)(FalconPlayer *player, const char *skinId, const uint8_t *rgba, uint32_t length, + uint32_t width, uint32_t height); } FalconServerApi; typedef int (*FalconPluginEntry)(const FalconServerApi *api, FalconPlugin *plugin, FalconPluginCallbacks *callbacks);