diff --git a/README.md b/README.md index 1139d74..96f51e3 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,23 @@ level.addTickingArea("spawn", {-16, 0, -16}, {16, 0, 16}); 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: + +```cpp +zombie.setEquipment(falcon::EquipmentSlot::Head, *falcon::Item::create("minecraft:diamond_helmet")); +player.entity().mount(horse); +zombie.addTag("boss"); +zombie.setAttackTarget(player.entity()); + +player.setSpawnPoint(level, {0, 80, 0}); +player.sendMessage("Your language: " + player.language()); +player.setCamera("minecraft:free", {0, 100, 0}, {30, 90, 0}, 2.0f, falcon::CameraEase::InOutSine); +player.fadeCamera(0.5f, 1.0f, 0.5f, 0x000000); +``` + ### C# plugins The `dotnet` runtime is deprecated. Write new plugins for Wasm, native C++ or Java. diff --git a/java/src/main/java/falcon/api/CameraEase.java b/java/src/main/java/falcon/api/CameraEase.java new file mode 100644 index 0000000..21d6e2c --- /dev/null +++ b/java/src/main/java/falcon/api/CameraEase.java @@ -0,0 +1,48 @@ +package falcon.api; + +import falcon.api.internal.FalconAbi; + +public enum CameraEase { + LINEAR(FalconAbi.CAMERA_EASE_LINEAR), + SPRING(FalconAbi.CAMERA_EASE_SPRING), + IN_SINE(FalconAbi.CAMERA_EASE_IN_SINE), + OUT_SINE(FalconAbi.CAMERA_EASE_OUT_SINE), + IN_OUT_SINE(FalconAbi.CAMERA_EASE_IN_OUT_SINE), + IN_QUAD(FalconAbi.CAMERA_EASE_IN_QUAD), + OUT_QUAD(FalconAbi.CAMERA_EASE_OUT_QUAD), + IN_OUT_QUAD(FalconAbi.CAMERA_EASE_IN_OUT_QUAD), + IN_CUBIC(FalconAbi.CAMERA_EASE_IN_CUBIC), + OUT_CUBIC(FalconAbi.CAMERA_EASE_OUT_CUBIC), + IN_OUT_CUBIC(FalconAbi.CAMERA_EASE_IN_OUT_CUBIC), + IN_QUART(FalconAbi.CAMERA_EASE_IN_QUART), + OUT_QUART(FalconAbi.CAMERA_EASE_OUT_QUART), + IN_OUT_QUART(FalconAbi.CAMERA_EASE_IN_OUT_QUART), + IN_QUINT(FalconAbi.CAMERA_EASE_IN_QUINT), + OUT_QUINT(FalconAbi.CAMERA_EASE_OUT_QUINT), + IN_OUT_QUINT(FalconAbi.CAMERA_EASE_IN_OUT_QUINT), + IN_EXPO(FalconAbi.CAMERA_EASE_IN_EXPO), + OUT_EXPO(FalconAbi.CAMERA_EASE_OUT_EXPO), + IN_OUT_EXPO(FalconAbi.CAMERA_EASE_IN_OUT_EXPO), + IN_CIRC(FalconAbi.CAMERA_EASE_IN_CIRC), + OUT_CIRC(FalconAbi.CAMERA_EASE_OUT_CIRC), + IN_OUT_CIRC(FalconAbi.CAMERA_EASE_IN_OUT_CIRC), + IN_BACK(FalconAbi.CAMERA_EASE_IN_BACK), + OUT_BACK(FalconAbi.CAMERA_EASE_OUT_BACK), + IN_OUT_BACK(FalconAbi.CAMERA_EASE_IN_OUT_BACK), + IN_ELASTIC(FalconAbi.CAMERA_EASE_IN_ELASTIC), + OUT_ELASTIC(FalconAbi.CAMERA_EASE_OUT_ELASTIC), + IN_OUT_ELASTIC(FalconAbi.CAMERA_EASE_IN_OUT_ELASTIC), + IN_BOUNCE(FalconAbi.CAMERA_EASE_IN_BOUNCE), + OUT_BOUNCE(FalconAbi.CAMERA_EASE_OUT_BOUNCE), + IN_OUT_BOUNCE(FalconAbi.CAMERA_EASE_IN_OUT_BOUNCE); + + private final int mValue; + + CameraEase(int value) { + mValue = value; + } + + public int value() { + return mValue; + } +} diff --git a/java/src/main/java/falcon/api/Entity.java b/java/src/main/java/falcon/api/Entity.java index ea828f1..fe5335c 100644 --- a/java/src/main/java/falcon/api/Entity.java +++ b/java/src/main/java/falcon/api/Entity.java @@ -241,6 +241,101 @@ public byte[] nbt() { } } + /** + * What a mob wears or holds; empty for an empty slot or an entity without equipment, players included. + */ + public Optional equipment(EquipmentSlot slot) { + return Item.adoptFilled(api().entityEquipment(mHandle, slot.value())); + } + + public boolean setEquipment(EquipmentSlot slot, Item item) { + return api().entitySetEquipment(mHandle, slot.value(), item.handle()) != 0; + } + + public boolean clearEquipment(EquipmentSlot slot) { + return api().entitySetEquipment(mHandle, slot.value(), MemorySegment.NULL) != 0; + } + + public Optional vehicle() { + return optional(api().entityVehicle(mHandle)); + } + + public List passengers() { + int count = api().entityPassengerCount(mHandle); + List result = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + optional(api().entityPassenger(mHandle, index)).ifPresent(result::add); + } + return result; + } + + /** + * Seats this entity on a vehicle that has a free seat, as when a player mounts it. + */ + public boolean mount(Entity vehicle) { + return api().entityMount(mHandle, vehicle.mHandle) != 0; + } + + public void dismount() { + api().entityDismount(mHandle); + } + + public void ejectPassengers() { + api().entityEjectPassengers(mHandle); + } + + /** + * The tags of /tag, saved with the entity. + */ + public List tags() { + int count = api().entityTagCount(mHandle); + List result = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + result.add(Interop.string(api().entityTag(mHandle, index))); + } + return result; + } + + public boolean hasTag(String tag) { + try (Arena arena = Arena.ofConfined()) { + return api().entityHasTag(mHandle, Interop.text(arena, tag)) != 0; + } + } + + public boolean addTag(String tag) { + try (Arena arena = Arena.ofConfined()) { + return api().entityAddTag(mHandle, Interop.text(arena, tag)) != 0; + } + } + + public boolean removeTag(String tag) { + try (Arena arena = Arena.ofConfined()) { + return api().entityRemoveTag(mHandle, Interop.text(arena, tag)) != 0; + } + } + + /** + * The entity a mob is attacking. + */ + public Optional attackTarget() { + return optional(api().entityAttackTarget(mHandle)); + } + + /** + * Makes a mob attack an entity; EntityTargetEvent listeners can refuse it. + */ + public boolean setAttackTarget(Entity target) { + return api().entitySetAttackTarget(mHandle, target.mHandle) != 0; + } + + public boolean clearAttackTarget() { + return api().entitySetAttackTarget(mHandle, MemorySegment.NULL) != 0; + } + + private Optional optional(MemorySegment entity) { + return Interop.isNull(entity) ? Optional.empty() : Optional.of(new Entity(mContext, entity)); + } + public Optional asPlayer() { MemorySegment player = api().entityPlayer(mHandle); if (Interop.isNull(player)) { diff --git a/java/src/main/java/falcon/api/EquipmentSlot.java b/java/src/main/java/falcon/api/EquipmentSlot.java new file mode 100644 index 0000000..7bc0ed1 --- /dev/null +++ b/java/src/main/java/falcon/api/EquipmentSlot.java @@ -0,0 +1,23 @@ +package falcon.api; + +import falcon.api.internal.FalconAbi; + +public enum EquipmentSlot { + MAINHAND(FalconAbi.EQUIPMENT_MAINHAND), + OFFHAND(FalconAbi.EQUIPMENT_OFFHAND), + HEAD(FalconAbi.EQUIPMENT_HEAD), + CHEST(FalconAbi.EQUIPMENT_CHEST), + LEGS(FalconAbi.EQUIPMENT_LEGS), + FEET(FalconAbi.EQUIPMENT_FEET), + BODY(FalconAbi.EQUIPMENT_BODY); + + private final int mValue; + + EquipmentSlot(int value) { + mValue = value; + } + + public int value() { + return mValue; + } +} diff --git a/java/src/main/java/falcon/api/Player.java b/java/src/main/java/falcon/api/Player.java index c189a4c..a564325 100644 --- a/java/src/main/java/falcon/api/Player.java +++ b/java/src/main/java/falcon/api/Player.java @@ -1,5 +1,6 @@ package falcon.api; +import falcon.api.internal.FalconAbi; import falcon.api.internal.Interop; import falcon.api.internal.NativeApi; import falcon.api.internal.PluginContext; @@ -7,6 +8,7 @@ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; +import java.util.Optional; public final class Player { private final PluginContext mContext; @@ -142,6 +144,104 @@ public void sendActionBar(String message) { } } + /** + * The spawn point a bed, an anchor or /spawnpoint set; empty when the player has none. + */ + public Optional spawnPoint() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment position = arena.allocate(FalconAbi.BlockPos.LAYOUT); + MemorySegment dimension = arena.allocate(ValueLayout.JAVA_INT); + if (api().playerSpawnPoint(mHandle, position, dimension) == 0) { + return Optional.empty(); + } + return Optional.of(new SpawnPoint(Interop.blockPos(position), + Dimension.of(dimension.get(ValueLayout.JAVA_INT, 0)))); + } + } + + public boolean setSpawnPoint(Level level, BlockPos position) { + try (Arena arena = Arena.ofConfined()) { + return api().playerSetSpawnPoint(mHandle, level.handle(), Interop.blockPos(arena, position)) != 0; + } + } + + public void clearSpawnPoint() { + api().playerClearSpawnPoint(mHandle); + } + + /** + * The language code of the client, such as en_US. + */ + public String language() { + return Interop.string(api().playerLanguage(mHandle)); + } + + /** + * The platform number the client sent at login, in the game's own numbering. + */ + public int deviceOs() { + return api().playerDeviceOs(mHandle); + } + + public String skinId() { + return Interop.string(api().playerSkinId(mHandle)); + } + + public SkinImage skinImage() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment width = arena.allocate(ValueLayout.JAVA_INT); + MemorySegment height = arena.allocate(ValueLayout.JAVA_INT); + MemorySegment length = arena.allocate(ValueLayout.JAVA_INT); + MemorySegment data = api().playerSkinImage(mHandle, width, height, length); + int size = length.get(ValueLayout.JAVA_INT, 0); + byte[] rgba = Interop.isNull(data) || size <= 0 ? new byte[0] + : data.reinterpret(size).toArray(ValueLayout.JAVA_BYTE); + return new SkinImage(width.get(ValueLayout.JAVA_INT, 0), height.get(ValueLayout.JAVA_INT, 0), rgba); + } + } + + /** + * Switches the camera to a preset such as minecraft:free, easing over easeTime seconds when above 0. + */ + public boolean setCamera(String preset, float easeTime, CameraEase ease) { + try (Arena arena = Arena.ofConfined()) { + return api().playerSetCamera(mHandle, Interop.text(arena, preset), MemorySegment.NULL, MemorySegment.NULL, + easeTime, ease.value()) != 0; + } + } + + /** + * A preset placed at a position and rotation (x pitch, y yaw, in degrees). + */ + public boolean setCamera(String preset, Vec3 position, Vec3 rotation, float easeTime, CameraEase ease) { + try (Arena arena = Arena.ofConfined()) { + return api().playerSetCamera(mHandle, Interop.text(arena, preset), Interop.vec3(arena, position), + Interop.vec3(arena, rotation), easeTime, ease.value()) != 0; + } + } + + /** + * Fades the screen to a 0xRRGGBB color and back, times in seconds. + */ + public boolean fadeCamera(float fadeIn, float hold, float fadeOut, int color) { + return api().playerFadeCamera(mHandle, fadeIn, hold, fadeOut, color) != 0; + } + + public void clearCamera() { + api().playerClearCamera(mHandle); + } + + /** + * Ticks left on the cooldown of the item's category, 0 when it is ready. + */ + public int itemCooldown(Item item) { + return api().playerItemCooldown(mHandle, item.handle()); + } + + public boolean startItemCooldown(Item item, int ticks) { + return api().playerStartItemCooldown(mHandle, item.handle(), ticks) != 0; + } + /** * Runs a command line, without the slash, as this player, with the player's permissions. */ diff --git a/java/src/main/java/falcon/api/SkinImage.java b/java/src/main/java/falcon/api/SkinImage.java new file mode 100644 index 0000000..26c7fd5 --- /dev/null +++ b/java/src/main/java/falcon/api/SkinImage.java @@ -0,0 +1,7 @@ +package falcon.api; + +/** + * A skin texture: RGBA, four bytes a pixel, rows from the top. + */ +public record SkinImage(int width, int height, byte[] rgba) { +} diff --git a/java/src/main/java/falcon/api/SpawnPoint.java b/java/src/main/java/falcon/api/SpawnPoint.java new file mode 100644 index 0000000..ca202e7 --- /dev/null +++ b/java/src/main/java/falcon/api/SpawnPoint.java @@ -0,0 +1,4 @@ +package falcon.api; + +public record SpawnPoint(BlockPos position, Dimension dimension) { +} diff --git a/native/include/falcon/Entity.hpp b/native/include/falcon/Entity.hpp index f49cee4..ac30444 100644 --- a/native/include/falcon/Entity.hpp +++ b/native/include/falcon/Entity.hpp @@ -2,6 +2,7 @@ #include "falcon/Api.hpp" #include "falcon/Effects.hpp" +#include "falcon/Item.hpp" #include "falcon/Vec3.hpp" #include @@ -13,6 +14,16 @@ namespace falcon { class Level; class Player; + enum class EquipmentSlot : uint32_t { + Mainhand = FALCON_EQUIPMENT_MAINHAND, + Offhand = FALCON_EQUIPMENT_OFFHAND, + Head = FALCON_EQUIPMENT_HEAD, + Chest = FALCON_EQUIPMENT_CHEST, + Legs = FALCON_EQUIPMENT_LEGS, + Feet = FALCON_EQUIPMENT_FEET, + Body = FALCON_EQUIPMENT_BODY + }; + class Entity { public: explicit Entity(FalconEntity *handle) : mHandle(handle) { @@ -220,6 +231,93 @@ namespace falcon { return std::vector(data, data + length); } + /** 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); + std::optional found = Item::adopt(detail::api().entityEquipment(mHandle, raw)); + if (found.has_value() && found->isEmpty()) + return std::nullopt; + return found; + } + + bool setEquipment(EquipmentSlot slot, const Item &item) const { + const FalconEquipmentSlot raw = static_cast(slot); + return detail::api().entitySetEquipment(mHandle, raw, item.handle()) != 0; + } + + bool clearEquipment(EquipmentSlot slot) const { + return detail::api().entitySetEquipment(mHandle, static_cast(slot), nullptr) != 0; + } + + std::optional vehicle() const { + FalconEntity *ridden = detail::api().entityVehicle(mHandle); + if (ridden == nullptr) + return std::nullopt; + return Entity(ridden); + } + + std::vector passengers() const { + std::vector result; + const uint32_t count = detail::api().entityPassengerCount(mHandle); + for (uint32_t index = 0; index < count; ++index) { + FalconEntity *rider = detail::api().entityPassenger(mHandle, index); + if (rider != nullptr) + result.emplace_back(rider); + } + return result; + } + + /** Seats this entity on a vehicle that has a free seat, as when a player mounts it. */ + bool mount(const Entity &vehicle) const { + return detail::api().entityMount(mHandle, vehicle.mHandle) != 0; + } + + void dismount() const { + detail::api().entityDismount(mHandle); + } + + void ejectPassengers() const { + detail::api().entityEjectPassengers(mHandle); + } + + /** The tags of /tag, saved with the entity. */ + std::vector tags() const { + std::vector result; + const uint32_t count = detail::api().entityTagCount(mHandle); + for (uint32_t index = 0; index < count; ++index) + result.push_back(detail::text(detail::api().entityTag(mHandle, index))); + return result; + } + + bool hasTag(const std::string &tag) const { + return detail::api().entityHasTag(mHandle, tag.c_str()) != 0; + } + + bool addTag(const std::string &tag) const { + return detail::api().entityAddTag(mHandle, tag.c_str()) != 0; + } + + bool removeTag(const std::string &tag) const { + return detail::api().entityRemoveTag(mHandle, tag.c_str()) != 0; + } + + /** The entity a mob is attacking. */ + std::optional attackTarget() const { + FalconEntity *target = detail::api().entityAttackTarget(mHandle); + if (target == nullptr) + return std::nullopt; + return Entity(target); + } + + /** Makes a mob attack an entity; EntityTargetEvent listeners can refuse it. */ + bool setAttackTarget(const Entity &target) const { + return detail::api().entitySetAttackTarget(mHandle, target.mHandle) != 0; + } + + bool clearAttackTarget() const { + return detail::api().entitySetAttackTarget(mHandle, nullptr) != 0; + } + std::optional asPlayer() const; FalconEntity *handle() const { diff --git a/native/include/falcon/Player.hpp b/native/include/falcon/Player.hpp index b1548cd..e0c2ac6 100644 --- a/native/include/falcon/Player.hpp +++ b/native/include/falcon/Player.hpp @@ -1,6 +1,8 @@ #pragma once #include "falcon/Api.hpp" +#include "falcon/BlockPos.hpp" +#include "falcon/Item.hpp" #include "falcon/PlayerInventory.hpp" #include "falcon/Vec3.hpp" @@ -12,6 +14,50 @@ namespace falcon { class Entity; + class Level; + enum class Dimension : uint32_t; + + enum class CameraEase : uint32_t { + Linear = FALCON_CAMERA_EASE_LINEAR, + Spring = FALCON_CAMERA_EASE_SPRING, + InSine = FALCON_CAMERA_EASE_IN_SINE, + OutSine = FALCON_CAMERA_EASE_OUT_SINE, + InOutSine = FALCON_CAMERA_EASE_IN_OUT_SINE, + InQuad = FALCON_CAMERA_EASE_IN_QUAD, + OutQuad = FALCON_CAMERA_EASE_OUT_QUAD, + InOutQuad = FALCON_CAMERA_EASE_IN_OUT_QUAD, + InCubic = FALCON_CAMERA_EASE_IN_CUBIC, + OutCubic = FALCON_CAMERA_EASE_OUT_CUBIC, + InOutCubic = FALCON_CAMERA_EASE_IN_OUT_CUBIC, + InQuart = FALCON_CAMERA_EASE_IN_QUART, + OutQuart = FALCON_CAMERA_EASE_OUT_QUART, + InOutQuart = FALCON_CAMERA_EASE_IN_OUT_QUART, + InQuint = FALCON_CAMERA_EASE_IN_QUINT, + OutQuint = FALCON_CAMERA_EASE_OUT_QUINT, + InOutQuint = FALCON_CAMERA_EASE_IN_OUT_QUINT, + InExpo = FALCON_CAMERA_EASE_IN_EXPO, + OutExpo = FALCON_CAMERA_EASE_OUT_EXPO, + InOutExpo = FALCON_CAMERA_EASE_IN_OUT_EXPO, + InCirc = FALCON_CAMERA_EASE_IN_CIRC, + OutCirc = FALCON_CAMERA_EASE_OUT_CIRC, + InOutCirc = FALCON_CAMERA_EASE_IN_OUT_CIRC, + InBack = FALCON_CAMERA_EASE_IN_BACK, + OutBack = FALCON_CAMERA_EASE_OUT_BACK, + InOutBack = FALCON_CAMERA_EASE_IN_OUT_BACK, + InElastic = FALCON_CAMERA_EASE_IN_ELASTIC, + OutElastic = FALCON_CAMERA_EASE_OUT_ELASTIC, + InOutElastic = FALCON_CAMERA_EASE_IN_OUT_ELASTIC, + InBounce = FALCON_CAMERA_EASE_IN_BOUNCE, + OutBounce = FALCON_CAMERA_EASE_OUT_BOUNCE, + InOutBounce = FALCON_CAMERA_EASE_IN_OUT_BOUNCE + }; + + struct SkinImage { + uint32_t width = 0; + uint32_t height = 0; + /** RGBA, four bytes a pixel, rows from the top. */ + std::vector rgba; + }; enum class GameMode : uint32_t { Survival = FALCON_GAME_MODE_SURVIVAL, @@ -124,6 +170,79 @@ namespace falcon { detail::api().playerSendActionBar(mHandle, message.c_str()); } + /** The spawn point a bed, an anchor or /spawnpoint set; false when the player has none. */ + bool spawnPoint(BlockPos &position, Dimension &dimension) const { + FalconBlockPos raw{}; + FalconDimension rawDimension = 0; + if (detail::api().playerSpawnPoint(mHandle, &raw, &rawDimension) == 0) + return false; + position = BlockPos::from(raw); + dimension = static_cast(rawDimension); + return true; + } + + bool setSpawnPoint(const Level &level, const BlockPos &position) const; + + void clearSpawnPoint() const { + detail::api().playerClearSpawnPoint(mHandle); + } + + /** The language code of the client, such as en_US. */ + std::string language() const { + return detail::text(detail::api().playerLanguage(mHandle)); + } + + /** The platform number the client sent at login, in the game's own numbering. */ + int32_t deviceOs() const { + return detail::api().playerDeviceOs(mHandle); + } + + std::string skinId() const { + return detail::text(detail::api().playerSkinId(mHandle)); + } + + SkinImage skinImage() const { + SkinImage image; + uint32_t length = 0; + const uint8_t *data = detail::api().playerSkinImage(mHandle, &image.width, &image.height, &length); + if (data != nullptr) + image.rgba.assign(data, data + length); + return image; + } + + /** 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, + static_cast(ease)) != 0; + } + + /** A preset placed at a position and rotation (x pitch, y yaw, in degrees). */ + bool setCamera(const std::string &preset, const Vec3 &position, const Vec3 &rotation, float easeTime = 0.0f, + CameraEase ease = CameraEase::Linear) const { + const FalconVec3 at = position.raw(); + const FalconVec3 turned = rotation.raw(); + return detail::api().playerSetCamera(mHandle, preset.c_str(), &at, &turned, easeTime, + static_cast(ease)) != 0; + } + + /** Fades the screen to a 0xRRGGBB color and back, times in seconds. */ + bool fadeCamera(float fadeIn, float hold, float fadeOut, uint32_t color = 0x000000) const { + return detail::api().playerFadeCamera(mHandle, fadeIn, hold, fadeOut, color) != 0; + } + + void clearCamera() const { + detail::api().playerClearCamera(mHandle); + } + + /** Ticks left on the cooldown of the item's category, 0 when it is ready. */ + int32_t itemCooldown(const Item &item) const { + return detail::api().playerItemCooldown(mHandle, item.handle()); + } + + bool startItemCooldown(const Item &item, int32_t ticks) const { + return detail::api().playerStartItemCooldown(mHandle, item.handle(), ticks) != 0; + } + /** Runs a command line, without the slash, as this player, with the player's permissions. */ bool executeCommand(const std::string &commandLine) const { return detail::api().playerExecuteCommand(mHandle, commandLine.c_str()) != 0; @@ -198,9 +317,14 @@ namespace falcon { } #include "falcon/Entity.hpp" +#include "falcon/Level.hpp" namespace falcon { inline Entity Player::entity() const { return Entity(detail::api().playerEntity(mHandle)); } + + inline bool Player::setSpawnPoint(const Level &level, const BlockPos &position) const { + return detail::api().playerSetSpawnPoint(mHandle, level.handle(), position.raw()) != 0; + } } diff --git a/native/include/falcon/falcon_api.h b/native/include/falcon/falcon_api.h index 194e14b..5b90173 100644 --- a/native/include/falcon/falcon_api.h +++ b/native/include/falcon/falcon_api.h @@ -212,6 +212,49 @@ typedef uint32_t FalconMirror; #define FALCON_MIRROR_Z 2u #define FALCON_MIRROR_XZ 3u +typedef uint32_t FalconEquipmentSlot; +#define FALCON_EQUIPMENT_MAINHAND 0u +#define FALCON_EQUIPMENT_OFFHAND 1u +#define FALCON_EQUIPMENT_HEAD 2u +#define FALCON_EQUIPMENT_CHEST 3u +#define FALCON_EQUIPMENT_LEGS 4u +#define FALCON_EQUIPMENT_FEET 5u +#define FALCON_EQUIPMENT_BODY 6u + +typedef uint32_t FalconCameraEase; +#define FALCON_CAMERA_EASE_LINEAR 0u +#define FALCON_CAMERA_EASE_SPRING 1u +#define FALCON_CAMERA_EASE_IN_SINE 2u +#define FALCON_CAMERA_EASE_OUT_SINE 3u +#define FALCON_CAMERA_EASE_IN_OUT_SINE 4u +#define FALCON_CAMERA_EASE_IN_QUAD 5u +#define FALCON_CAMERA_EASE_OUT_QUAD 6u +#define FALCON_CAMERA_EASE_IN_OUT_QUAD 7u +#define FALCON_CAMERA_EASE_IN_CUBIC 8u +#define FALCON_CAMERA_EASE_OUT_CUBIC 9u +#define FALCON_CAMERA_EASE_IN_OUT_CUBIC 10u +#define FALCON_CAMERA_EASE_IN_QUART 11u +#define FALCON_CAMERA_EASE_OUT_QUART 12u +#define FALCON_CAMERA_EASE_IN_OUT_QUART 13u +#define FALCON_CAMERA_EASE_IN_QUINT 14u +#define FALCON_CAMERA_EASE_OUT_QUINT 15u +#define FALCON_CAMERA_EASE_IN_OUT_QUINT 16u +#define FALCON_CAMERA_EASE_IN_EXPO 17u +#define FALCON_CAMERA_EASE_OUT_EXPO 18u +#define FALCON_CAMERA_EASE_IN_OUT_EXPO 19u +#define FALCON_CAMERA_EASE_IN_CIRC 20u +#define FALCON_CAMERA_EASE_OUT_CIRC 21u +#define FALCON_CAMERA_EASE_IN_OUT_CIRC 22u +#define FALCON_CAMERA_EASE_IN_BACK 23u +#define FALCON_CAMERA_EASE_OUT_BACK 24u +#define FALCON_CAMERA_EASE_IN_OUT_BACK 25u +#define FALCON_CAMERA_EASE_IN_ELASTIC 26u +#define FALCON_CAMERA_EASE_OUT_ELASTIC 27u +#define FALCON_CAMERA_EASE_IN_OUT_ELASTIC 28u +#define FALCON_CAMERA_EASE_IN_BOUNCE 29u +#define FALCON_CAMERA_EASE_OUT_BOUNCE 30u +#define FALCON_CAMERA_EASE_IN_OUT_BOUNCE 31u + typedef uint32_t FalconLogLevel; #define FALCON_LOG_INFO 0u #define FALCON_LOG_WARNING 1u @@ -666,6 +709,36 @@ typedef struct FalconServerApi { int (*levelRemoveTickingArea)(FalconLevel *level, const char *name); uint32_t (*levelTickingAreaCount)(FalconLevel *level); const char *(*levelTickingAreaName)(FalconLevel *level, uint32_t index); + + FalconItem *(*entityEquipment)(FalconEntity *entity, FalconEquipmentSlot slot); + int (*entitySetEquipment)(FalconEntity *entity, FalconEquipmentSlot slot, FalconItem *item); + FalconEntity *(*entityVehicle)(FalconEntity *entity); + uint32_t (*entityPassengerCount)(FalconEntity *entity); + FalconEntity *(*entityPassenger)(FalconEntity *entity, uint32_t index); + int (*entityMount)(FalconEntity *rider, FalconEntity *vehicle); + void (*entityDismount)(FalconEntity *rider); + void (*entityEjectPassengers)(FalconEntity *vehicle); + uint32_t (*entityTagCount)(FalconEntity *entity); + const char *(*entityTag)(FalconEntity *entity, uint32_t index); + int (*entityHasTag)(FalconEntity *entity, const char *tag); + int (*entityAddTag)(FalconEntity *entity, const char *tag); + int (*entityRemoveTag)(FalconEntity *entity, const char *tag); + FalconEntity *(*entityAttackTarget)(FalconEntity *entity); + int (*entitySetAttackTarget)(FalconEntity *entity, FalconEntity *target); + + int (*playerSpawnPoint)(FalconPlayer *player, FalconBlockPos *position, FalconDimension *dimension); + int (*playerSetSpawnPoint)(FalconPlayer *player, FalconLevel *level, FalconBlockPos position); + void (*playerClearSpawnPoint)(FalconPlayer *player); + const char *(*playerLanguage)(FalconPlayer *player); + int32_t (*playerDeviceOs)(FalconPlayer *player); + const char *(*playerSkinId)(FalconPlayer *player); + const uint8_t *(*playerSkinImage)(FalconPlayer *player, uint32_t *width, uint32_t *height, uint32_t *length); + int (*playerSetCamera)(FalconPlayer *player, const char *preset, const FalconVec3 *position, + const FalconVec3 *rotation, float easeTime, FalconCameraEase ease); + int (*playerFadeCamera)(FalconPlayer *player, float fadeIn, float hold, float fadeOut, uint32_t color); + void (*playerClearCamera)(FalconPlayer *player); + int32_t (*playerItemCooldown)(FalconPlayer *player, FalconItem *item); + int (*playerStartItemCooldown)(FalconPlayer *player, FalconItem *item, int32_t ticks); } FalconServerApi; typedef int (*FalconPluginEntry)(const FalconServerApi *api, FalconPlugin *plugin, FalconPluginCallbacks *callbacks); diff --git a/wasm/falcon.wit b/wasm/falcon.wit index 40c1518..d18e33a 100644 --- a/wasm/falcon.wit +++ b/wasm/falcon.wit @@ -134,7 +134,19 @@ interface server { } interface players { - use types.{player, entity, game-mode, vec3}; + use types.{player, entity, game-mode, vec3, block-pos, dimension, level, item}; + + record spawn-point { + position: block-pos, + dimension: dimension, + } + + /// RGBA, four bytes a pixel, rows from the top. + record skin-image { + width: u32, + height: u32, + rgba: list, + } player-name: func(player: player) -> string; player-is-operator: func(player: player) -> bool; @@ -166,10 +178,26 @@ interface players { player-stop-sound: func(player: player, sound: option); player-spawn-particle: func(player: player, particle: string, position: vec3); player-execute-command: func(player: player, command-line: string) -> bool; + player-spawn-point: func(player: player) -> option; + player-set-spawn-point: func(player: player, level: borrow, position: block-pos) -> bool; + player-clear-spawn-point: func(player: player); + player-language: func(player: player) -> string; + /// The platform number the client sent at login, in the game's own numbering. + player-device-os: func(player: player) -> s32; + player-skin-id: func(player: player) -> string; + player-skin-image: func(player: player) -> skin-image; + /// `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; + /// `color`: 0xRRGGBB; times in seconds. + player-fade-camera: func(player: player, fade-in: f32, hold: f32, fade-out: f32, color: u32) -> bool; + player-clear-camera: func(player: player); + player-item-cooldown: func(player: player, item: borrow) -> s32; + player-start-item-cooldown: func(player: player, item: borrow, ticks: s32) -> bool; } interface entities { - use types.{entity, level, vec3, effect-type, effect, attribute}; + use types.{entity, level, item, vec3, effect-type, effect, attribute}; entity-type: func(entity: entity) -> string; entity-runtime-id: func(entity: entity) -> u64; @@ -213,6 +241,23 @@ 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; + /// `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; + entity-vehicle: func(entity: entity) -> option; + entity-passenger-count: func(entity: entity) -> u32; + entity-passenger: func(entity: entity, index: u32) -> option; + entity-mount: func(rider: entity, vehicle: entity) -> bool; + entity-dismount: func(rider: entity); + entity-eject-passengers: func(vehicle: entity); + entity-tag-count: func(entity: entity) -> u32; + entity-tag: func(entity: entity, index: u32) -> option; + entity-has-tag: func(entity: entity, tag: string) -> bool; + entity-add-tag: func(entity: entity, tag: string) -> bool; + entity-remove-tag: func(entity: entity, tag: string) -> bool; + entity-attack-target: func(entity: entity) -> option; + /// `none` clears the target. + entity-set-attack-target: func(entity: entity, target: option) -> bool; } interface levels {