From 7caef0e7c5541290fcef9a6c1733b1587230ea99 Mon Sep 17 00:00:00 2001 From: NaySurGithub <96527285+NaySurGithub@users.noreply.github.com> Date: Sat, 14 Nov 2026 21:23:43 +0100 Subject: [PATCH] feat: add game rules, difficulty, biomes, rays, area queries, structures and ticking areas to the plugin api --- README.md | 21 +++ java/src/main/java/falcon/api/BlockFace.java | 31 ++++ java/src/main/java/falcon/api/BlockHit.java | 7 + java/src/main/java/falcon/api/Difficulty.java | 29 ++++ java/src/main/java/falcon/api/EntityHit.java | 4 + java/src/main/java/falcon/api/Level.java | 134 +++++++++++++++++ java/src/main/java/falcon/api/Mirror.java | 29 ++++ java/src/main/java/falcon/api/Server.java | 11 ++ native/include/falcon/Level.hpp | 140 ++++++++++++++++++ native/include/falcon/Server.hpp | 16 ++ native/include/falcon/falcon_api.h | 49 ++++++ wasm/falcon.wit | 37 ++++- 12 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 java/src/main/java/falcon/api/BlockFace.java create mode 100644 java/src/main/java/falcon/api/BlockHit.java create mode 100644 java/src/main/java/falcon/api/Difficulty.java create mode 100644 java/src/main/java/falcon/api/EntityHit.java create mode 100644 java/src/main/java/falcon/api/Mirror.java diff --git a/README.md b/README.md index 1e5eca3..1139d74 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,27 @@ falcon::Recipes::addShapeless("myplugin:dye", {"minecraft:dandelion"}, "minecraf falcon::Recipes::remove("myplugin:dye"); ``` +### World + +Levels expose the rules and state of their world: game rules, the biome at a position, rays, entities in a box, +structures and ticking areas. The difficulty belongs to the server: + +```cpp +level.setGameRule("keepinventory", "true"); +std::string biome = level.biome({0, 64, 0}); + +const falcon::Entity eyes = player.entity(); +if (std::optional hit = level.raycastBlock(eyes.position(), {0, -1, 0}, 16.0)) + player.sendMessage("standing over " + level.blockName(hit->block)); + +for (const falcon::Entity &nearby: level.entitiesInBox({-8, 60, -8}, {8, 70, 8})) + nearby.setOnFire(60); + +level.placeStructure("mystructure:house", {100, 64, 100}, 1); +level.addTickingArea("spawn", {-16, 0, -16}, {16, 0, 16}); +falcon::Server::setDifficulty(falcon::Difficulty::Hard); +``` + ### 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/BlockFace.java b/java/src/main/java/falcon/api/BlockFace.java new file mode 100644 index 0000000..fde39ec --- /dev/null +++ b/java/src/main/java/falcon/api/BlockFace.java @@ -0,0 +1,31 @@ +package falcon.api; + +import falcon.api.internal.FalconAbi; + +public enum BlockFace { + DOWN(FalconAbi.FACE_DOWN), + UP(FalconAbi.FACE_UP), + NORTH(FalconAbi.FACE_NORTH), + SOUTH(FalconAbi.FACE_SOUTH), + WEST(FalconAbi.FACE_WEST), + EAST(FalconAbi.FACE_EAST); + + private final int mValue; + + BlockFace(int value) { + mValue = value; + } + + public int value() { + return mValue; + } + + public static BlockFace of(int value) { + for (BlockFace entry : values()) { + if (entry.mValue == value) { + return entry; + } + } + throw new IllegalArgumentException("Unknown value " + value); + } +} diff --git a/java/src/main/java/falcon/api/BlockHit.java b/java/src/main/java/falcon/api/BlockHit.java new file mode 100644 index 0000000..08a60a3 --- /dev/null +++ b/java/src/main/java/falcon/api/BlockHit.java @@ -0,0 +1,7 @@ +package falcon.api; + +/** + * The block a ray hit; position is where the ray enters it. + */ +public record BlockHit(BlockPos block, BlockFace face, Vec3 position, double distance) { +} diff --git a/java/src/main/java/falcon/api/Difficulty.java b/java/src/main/java/falcon/api/Difficulty.java new file mode 100644 index 0000000..22f3e46 --- /dev/null +++ b/java/src/main/java/falcon/api/Difficulty.java @@ -0,0 +1,29 @@ +package falcon.api; + +import falcon.api.internal.FalconAbi; + +public enum Difficulty { + PEACEFUL(FalconAbi.DIFFICULTY_PEACEFUL), + EASY(FalconAbi.DIFFICULTY_EASY), + NORMAL(FalconAbi.DIFFICULTY_NORMAL), + HARD(FalconAbi.DIFFICULTY_HARD); + + private final int mValue; + + Difficulty(int value) { + mValue = value; + } + + public int value() { + return mValue; + } + + public static Difficulty of(int value) { + for (Difficulty entry : values()) { + if (entry.mValue == value) { + return entry; + } + } + throw new IllegalArgumentException("Unknown value " + value); + } +} diff --git a/java/src/main/java/falcon/api/EntityHit.java b/java/src/main/java/falcon/api/EntityHit.java new file mode 100644 index 0000000..fd2613e --- /dev/null +++ b/java/src/main/java/falcon/api/EntityHit.java @@ -0,0 +1,4 @@ +package falcon.api; + +public record EntityHit(Entity entity, double distance) { +} diff --git a/java/src/main/java/falcon/api/Level.java b/java/src/main/java/falcon/api/Level.java index 68da5ac..e59a8ec 100644 --- a/java/src/main/java/falcon/api/Level.java +++ b/java/src/main/java/falcon/api/Level.java @@ -1,11 +1,13 @@ package falcon.api; +import falcon.api.internal.FalconAbi; import falcon.api.internal.Interop; import falcon.api.internal.NativeApi; import falcon.api.internal.PluginContext; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -215,6 +217,138 @@ public boolean setSignText(BlockPos position, String text, SignSide side) { } } + public List gameRules() { + int count = api().levelGameRuleCount(mHandle); + List names = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + names.add(Interop.string(api().levelGameRuleName(mHandle, index))); + } + return names; + } + + /** + * "true" or "false" for a switch, the number otherwise; the rules belong to the level's world. + */ + public Optional gameRule(String name) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment value = api().levelGameRule(mHandle, Interop.text(arena, name)); + return Interop.isNull(value) ? Optional.empty() : Optional.of(Interop.string(value)); + } + } + + public boolean setGameRule(String name, String value) { + try (Arena arena = Arena.ofConfined()) { + return api().levelSetGameRule(mHandle, Interop.text(arena, name), Interop.text(arena, value)) != 0; + } + } + + /** + * The biome identifier at a position, such as minecraft:plains. + */ + public String biome(BlockPos position) { + try (Arena arena = Arena.ofConfined()) { + return Interop.string(api().levelBiome(mHandle, Interop.blockPos(arena, position))); + } + } + + /** + * The first block on the ray; an unloaded chunk stops it without a hit. + */ + public Optional raycastBlock(Vec3 origin, Vec3 direction, double maxDistance, boolean includeLiquids, + boolean includePassable) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment hit = arena.allocate(FalconAbi.BlockHit.LAYOUT); + if (api().levelRaycastBlock(mHandle, Interop.vec3(arena, origin), Interop.vec3(arena, direction), + maxDistance, includeLiquids ? 1 : 0, includePassable ? 1 : 0, hit) == 0) { + return Optional.empty(); + } + BlockPos block = Interop.blockPos(hit.asSlice(FalconAbi.BlockHit.BLOCK, FalconAbi.BlockPos.LAYOUT)); + Vec3 position = Interop.vec3(hit.asSlice(FalconAbi.BlockHit.POSITION, FalconAbi.Vec3.LAYOUT)); + return Optional.of(new BlockHit(block, BlockFace.of(hit.get(ValueLayout.JAVA_INT, FalconAbi.BlockHit.FACE)), + position, hit.get(ValueLayout.JAVA_DOUBLE, FalconAbi.BlockHit.DISTANCE))); + } + } + + /** + * The nearest entity on the ray, before any block; except, usually the one looking, may be null. + */ + public Optional raycastEntity(Vec3 origin, Vec3 direction, double maxDistance, Entity except) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment distance = arena.allocate(ValueLayout.JAVA_DOUBLE); + MemorySegment hit = api().levelRaycastEntity(mHandle, Interop.vec3(arena, origin), + Interop.vec3(arena, direction), maxDistance, except == null ? MemorySegment.NULL : except.handle(), + distance); + if (Interop.isNull(hit)) { + return Optional.empty(); + } + return Optional.of(new EntityHit(new Entity(mContext, hit), distance.get(ValueLayout.JAVA_DOUBLE, 0))); + } + } + + /** + * Every entity and player whose box touches the box between two corners. + */ + public List entitiesInBox(Vec3 from, Vec3 to) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment low = Interop.vec3(arena, from); + MemorySegment high = Interop.vec3(arena, to); + int capacity = 64; + MemorySegment handles = arena.allocate(ValueLayout.ADDRESS, capacity); + int count = api().levelEntitiesInBox(mHandle, low, high, handles, capacity); + if (count > capacity) { + capacity = count; + handles = arena.allocate(ValueLayout.ADDRESS, capacity); + count = api().levelEntitiesInBox(mHandle, low, high, handles, capacity); + } + List result = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < Math.min(count, capacity); index++) { + result.add(new Entity(mContext, handles.getAtIndex(ValueLayout.ADDRESS, index))); + } + return result; + } + } + + public boolean placeStructure(String structure, BlockPos position) { + return placeStructure(structure, position, 0, Mirror.NONE, true, 1.0f); + } + + /** + * Places a saved structure by identifier with its lowest corner at position. Rotation is in quarter turns + * clockwise; integrity keeps that share of blocks. + */ + public boolean placeStructure(String structure, BlockPos position, int rotation, Mirror mirror, + boolean includeEntities, float integrity) { + try (Arena arena = Arena.ofConfined()) { + return api().levelPlaceStructure(mHandle, Interop.text(arena, structure), Interop.blockPos(arena, position), + rotation, mirror.value(), includeEntities ? 1 : 0, integrity) != 0; + } + } + + /** + * Keeps the chunks between two corners loaded and ticking, with the limits of /tickingarea. + */ + public boolean addTickingArea(String name, BlockPos from, BlockPos to, boolean preload) { + try (Arena arena = Arena.ofConfined()) { + return api().levelAddTickingArea(mHandle, Interop.text(arena, name), Interop.blockPos(arena, from), + Interop.blockPos(arena, to), preload ? 1 : 0) != 0; + } + } + + public boolean removeTickingArea(String name) { + try (Arena arena = Arena.ofConfined()) { + return api().levelRemoveTickingArea(mHandle, Interop.text(arena, name)) != 0; + } + } + + public List tickingAreas() { + int count = api().levelTickingAreaCount(mHandle); + List names = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + names.add(Interop.string(api().levelTickingAreaName(mHandle, index))); + } + return names; + } + public List entities() { int count = api().levelEntityCount(mHandle); List result = new ArrayList<>(count); diff --git a/java/src/main/java/falcon/api/Mirror.java b/java/src/main/java/falcon/api/Mirror.java new file mode 100644 index 0000000..d99b691 --- /dev/null +++ b/java/src/main/java/falcon/api/Mirror.java @@ -0,0 +1,29 @@ +package falcon.api; + +import falcon.api.internal.FalconAbi; + +public enum Mirror { + NONE(FalconAbi.MIRROR_NONE), + X(FalconAbi.MIRROR_X), + Z(FalconAbi.MIRROR_Z), + XZ(FalconAbi.MIRROR_XZ); + + private final int mValue; + + Mirror(int value) { + mValue = value; + } + + public int value() { + return mValue; + } + + public static Mirror of(int value) { + for (Mirror entry : values()) { + if (entry.mValue == value) { + return entry; + } + } + throw new IllegalArgumentException("Unknown value " + value); + } +} diff --git a/java/src/main/java/falcon/api/Server.java b/java/src/main/java/falcon/api/Server.java index 4766c84..26d8c5d 100644 --- a/java/src/main/java/falcon/api/Server.java +++ b/java/src/main/java/falcon/api/Server.java @@ -97,6 +97,17 @@ public boolean unloadWorld(String name) { } } + public Difficulty difficulty() { + return Difficulty.of(api().serverDifficulty()); + } + + /** + * Saved in the server properties and sent to every player, like /difficulty. + */ + public boolean setDifficulty(Difficulty difficulty) { + return api().serverSetDifficulty(difficulty.value()) != 0; + } + public Scoreboard scoreboard() { return new Scoreboard(mContext); } diff --git a/native/include/falcon/Level.hpp b/native/include/falcon/Level.hpp index 5ff4594..3b09390 100644 --- a/native/include/falcon/Level.hpp +++ b/native/include/falcon/Level.hpp @@ -19,6 +19,32 @@ namespace falcon { Back = FALCON_SIGN_BACK }; + enum class BlockFace : uint32_t { + Down = FALCON_FACE_DOWN, + Up = FALCON_FACE_UP, + North = FALCON_FACE_NORTH, + South = FALCON_FACE_SOUTH, + West = FALCON_FACE_WEST, + East = FALCON_FACE_EAST + }; + + enum class Mirror : uint32_t { + None = FALCON_MIRROR_NONE, + X = FALCON_MIRROR_X, + Z = FALCON_MIRROR_Z, + XZ = FALCON_MIRROR_XZ + }; + + struct BlockHit { + BlockPos block; + BlockFace face = BlockFace::Up; + /** Where the ray enters the block. */ + Vec3 position; + double distance = 0.0; + }; + + struct EntityHit; + enum class Dimension : uint32_t { Overworld = FALCON_DIMENSION_OVERWORLD, Nether = FALCON_DIMENSION_NETHER, @@ -156,6 +182,80 @@ namespace falcon { text.c_str()) != 0; } + std::vector gameRules() const { + std::vector names; + const uint32_t count = detail::api().levelGameRuleCount(mHandle); + for (uint32_t index = 0; index < count; ++index) + names.push_back(detail::text(detail::api().levelGameRuleName(mHandle, index))); + return names; + } + + /** "true" or "false" for a switch, the number otherwise; the rules belong to the level's world. */ + std::optional gameRule(const std::string &name) const { + const char *value = detail::api().levelGameRule(mHandle, name.c_str()); + if (value == nullptr) + return std::nullopt; + return std::string(value); + } + + bool setGameRule(const std::string &name, const std::string &value) const { + return detail::api().levelSetGameRule(mHandle, name.c_str(), value.c_str()) != 0; + } + + /** The biome identifier at a position, such as minecraft:plains. */ + std::string biome(const BlockPos &position) const { + return detail::text(detail::api().levelBiome(mHandle, position.raw())); + } + + /** The first block on the ray; an unloaded chunk stops it without a hit. */ + std::optional raycastBlock(const Vec3 &origin, const Vec3 &direction, double maxDistance, + bool includeLiquids = false, bool includePassable = false) const { + FalconBlockHit hit{}; + if (detail::api().levelRaycastBlock(mHandle, origin.raw(), direction.raw(), maxDistance, + includeLiquids ? 1 : 0, includePassable ? 1 : 0, &hit) == 0) + return std::nullopt; + return BlockHit{BlockPos::from(hit.block), static_cast(hit.face), Vec3::from(hit.position), + hit.distance}; + } + + /** The nearest entity on the ray, before any block; except is left out, usually the one looking. */ + std::optional raycastEntity(const Vec3 &origin, const Vec3 &direction, double maxDistance) const; + + std::optional raycastEntity(const Vec3 &origin, const Vec3 &direction, double maxDistance, + const Entity &except) const; + + /** Every entity and player whose box touches the box between two corners. */ + std::vector entitiesInBox(const Vec3 &from, const Vec3 &to) const; + + /** + * Places a structure saved by the structure manager, a pack or a structure block, by identifier, with its + * lowest corner at position. Rotation is in quarter turns clockwise; integrity keeps that share of blocks. + */ + bool placeStructure(const std::string &structure, const BlockPos &position, uint32_t rotation = 0, + Mirror mirror = Mirror::None, bool includeEntities = true, float integrity = 1.0f) const { + return detail::api().levelPlaceStructure(mHandle, structure.c_str(), position.raw(), rotation, + static_cast(mirror), includeEntities ? 1 : 0, + integrity) != 0; + } + + /** Keeps the chunks between two corners loaded and ticking, with the limits of /tickingarea. */ + bool addTickingArea(const std::string &name, const BlockPos &from, const BlockPos &to, + bool preload = false) const { + return detail::api().levelAddTickingArea(mHandle, name.c_str(), from.raw(), to.raw(), preload ? 1 : 0) != 0; + } + + bool removeTickingArea(const std::string &name) const { + return detail::api().levelRemoveTickingArea(mHandle, name.c_str()) != 0; + } + + std::vector tickingAreas() const { + std::vector names; + const uint32_t count = detail::api().levelTickingAreaCount(mHandle); + for (uint32_t index = 0; index < count; ++index) + names.push_back(detail::text(detail::api().levelTickingAreaName(mHandle, index))); + return names; + } + std::vector entities() const; FalconLevel *handle() const { @@ -170,6 +270,46 @@ namespace falcon { #include "falcon/Entity.hpp" namespace falcon { + struct EntityHit { + Entity entity; + double distance = 0.0; + }; + + inline std::optional Level::raycastEntity(const Vec3 &origin, const Vec3 &direction, + double maxDistance) const { + double distance = 0.0; + FalconEntity *hit = detail::api().levelRaycastEntity(mHandle, origin.raw(), direction.raw(), maxDistance, + nullptr, &distance); + if (hit == nullptr) + return std::nullopt; + return EntityHit{Entity(hit), distance}; + } + + inline std::optional Level::raycastEntity(const Vec3 &origin, const Vec3 &direction, double maxDistance, + const Entity &except) const { + double distance = 0.0; + FalconEntity *hit = detail::api().levelRaycastEntity(mHandle, origin.raw(), direction.raw(), maxDistance, + except.handle(), &distance); + if (hit == nullptr) + return std::nullopt; + return EntityHit{Entity(hit), distance}; + } + + inline std::vector Level::entitiesInBox(const Vec3 &from, const Vec3 &to) const { + std::vector handles(64); + uint32_t count = detail::api().levelEntitiesInBox(mHandle, from.raw(), to.raw(), handles.data(), + static_cast(handles.size())); + if (count > handles.size()) { + handles.resize(count); + count = detail::api().levelEntitiesInBox(mHandle, from.raw(), to.raw(), handles.data(), count); + } + + std::vector result; + for (uint32_t index = 0; index < count && index < handles.size(); ++index) + result.emplace_back(handles[index]); + return result; + } + inline std::optional Level::spawnEntity(const std::string &identifier, const Vec3 &position) const { FalconEntity *entity = detail::api().levelSpawnEntity(mHandle, identifier.c_str(), position.raw()); if (entity == nullptr) diff --git a/native/include/falcon/Server.hpp b/native/include/falcon/Server.hpp index 3bb1658..d11033d 100644 --- a/native/include/falcon/Server.hpp +++ b/native/include/falcon/Server.hpp @@ -8,8 +8,24 @@ #include namespace falcon { + enum class Difficulty : uint32_t { + Peaceful = FALCON_DIFFICULTY_PEACEFUL, + Easy = FALCON_DIFFICULTY_EASY, + Normal = FALCON_DIFFICULTY_NORMAL, + Hard = FALCON_DIFFICULTY_HARD + }; + class Server { public: + static Difficulty difficulty() { + return static_cast(detail::api().serverDifficulty()); + } + + /** Saved in the server properties and sent to every player, like /difficulty. */ + static bool setDifficulty(Difficulty difficulty) { + return detail::api().serverSetDifficulty(static_cast(difficulty)) != 0; + } + static std::string version() { return detail::api().serverVersion(); } diff --git a/native/include/falcon/falcon_api.h b/native/include/falcon/falcon_api.h index 04fd196..194e14b 100644 --- a/native/include/falcon/falcon_api.h +++ b/native/include/falcon/falcon_api.h @@ -192,6 +192,26 @@ typedef uint32_t FalconSignSide; #define FALCON_SIGN_FRONT 0u #define FALCON_SIGN_BACK 1u +typedef uint32_t FalconDifficulty; +#define FALCON_DIFFICULTY_PEACEFUL 0u +#define FALCON_DIFFICULTY_EASY 1u +#define FALCON_DIFFICULTY_NORMAL 2u +#define FALCON_DIFFICULTY_HARD 3u + +typedef uint32_t FalconBlockFace; +#define FALCON_FACE_DOWN 0u +#define FALCON_FACE_UP 1u +#define FALCON_FACE_NORTH 2u +#define FALCON_FACE_SOUTH 3u +#define FALCON_FACE_WEST 4u +#define FALCON_FACE_EAST 5u + +typedef uint32_t FalconMirror; +#define FALCON_MIRROR_NONE 0u +#define FALCON_MIRROR_X 1u +#define FALCON_MIRROR_Z 2u +#define FALCON_MIRROR_XZ 3u + typedef uint32_t FalconLogLevel; #define FALCON_LOG_INFO 0u #define FALCON_LOG_WARNING 1u @@ -320,6 +340,14 @@ typedef struct FalconAttribute { float defaultValue; } FalconAttribute; +typedef struct FalconBlockHit { + FalconBlockPos block; + FalconBlockFace face; + /* where the ray enters the block */ + FalconVec3 position; + double distance; +} FalconBlockHit; + typedef struct FalconServerApi { uint32_t size; uint32_t versionMajor; @@ -617,6 +645,27 @@ typedef struct FalconServerApi { int (*playerExecuteCommand)(FalconPlayer *player, const char *commandLine); int (*registerRecipe)(FalconPlugin *plugin, const FalconRecipeDescriptor *descriptor); int (*removeRecipe)(const char *identifier); + + uint32_t (*levelGameRuleCount)(FalconLevel *level); + const char *(*levelGameRuleName)(FalconLevel *level, uint32_t index); + const char *(*levelGameRule)(FalconLevel *level, const char *name); + int (*levelSetGameRule)(FalconLevel *level, const char *name, const char *value); + FalconDifficulty (*serverDifficulty)(void); + int (*serverSetDifficulty)(FalconDifficulty difficulty); + const char *(*levelBiome)(FalconLevel *level, FalconBlockPos position); + int (*levelRaycastBlock)(FalconLevel *level, FalconVec3 origin, FalconVec3 direction, double maxDistance, + int includeLiquids, int includePassable, FalconBlockHit *out); + FalconEntity *(*levelRaycastEntity)(FalconLevel *level, FalconVec3 origin, FalconVec3 direction, double maxDistance, + FalconEntity *except, double *distance); + uint32_t (*levelEntitiesInBox)(FalconLevel *level, FalconVec3 from, FalconVec3 to, FalconEntity **entities, + uint32_t capacity); + int (*levelPlaceStructure)(FalconLevel *level, const char *structure, FalconBlockPos position, uint32_t rotation, + FalconMirror mirror, int includeEntities, float integrity); + int (*levelAddTickingArea)(FalconLevel *level, const char *name, FalconBlockPos from, FalconBlockPos to, + int preload); + int (*levelRemoveTickingArea)(FalconLevel *level, const char *name); + uint32_t (*levelTickingAreaCount)(FalconLevel *level); + const char *(*levelTickingAreaName)(FalconLevel *level, uint32_t index); } FalconServerApi; typedef int (*FalconPluginEntry)(const FalconServerApi *api, FalconPlugin *plugin, FalconPluginCallbacks *callbacks); diff --git a/wasm/falcon.wit b/wasm/falcon.wit index e970aa4..40c1518 100644 --- a/wasm/falcon.wit +++ b/wasm/falcon.wit @@ -95,6 +95,19 @@ interface types { default-value: f32, } + /// `face`: one of the `FALCON_FACE_*` values; `position`: where the ray enters the block. + record block-hit { + block: block-pos, + face: u32, + position: vec3, + distance: f64, + } + + record entity-hit { + entity: entity, + distance: f64, + } + /// A level of a loaded world. A level whose world was unloaded behaves like a missing one. resource level; @@ -115,6 +128,9 @@ interface server { online-player: func(index: u32) -> option; find-player: func(name: string) -> option; broadcast-message: func(message: string); + /// One of the `FALCON_DIFFICULTY_*` values. + server-difficulty: func() -> u32; + server-set-difficulty: func(difficulty: u32) -> bool; } interface players { @@ -200,7 +216,7 @@ interface entities { } interface levels { - use types.{entity, dimension, level, item, vec3, block-pos}; + use types.{entity, dimension, level, item, vec3, block-pos, block-hit, entity-hit}; server-level: func(dimension: dimension) -> option; level-dimension: func(level: borrow) -> dimension; @@ -239,6 +255,25 @@ interface levels { /// `side`: `FALCON_SIGN_FRONT` or `FALCON_SIGN_BACK`. level-sign-text: func(level: borrow, position: block-pos, side: u32) -> option; level-set-sign-text: func(level: borrow, position: block-pos, side: u32, text: string) -> bool; + level-game-rule-count: func(level: borrow) -> u32; + level-game-rule-name: func(level: borrow, index: u32) -> option; + /// "true" or "false" for a switch, the number otherwise. + level-game-rule: func(level: borrow, name: string) -> option; + level-set-game-rule: func(level: borrow, name: string, value: string) -> bool; + level-biome: func(level: borrow, position: block-pos) -> string; + level-raycast-block: func(level: borrow, origin: vec3, direction: vec3, max-distance: f64, + include-liquids: bool, include-passable: bool) -> option; + level-raycast-entity: func(level: borrow, origin: vec3, direction: vec3, max-distance: f64, + except: option) -> option; + level-entities-in-box: func(level: borrow, low: vec3, high: vec3) -> list; + /// `mirror`: one of the `FALCON_MIRROR_*` values; `rotation` in quarter turns clockwise. + level-place-structure: func(level: borrow, structure: string, position: block-pos, rotation: u32, + mirror: u32, include-entities: bool, integrity: f32) -> bool; + level-add-ticking-area: func(level: borrow, name: string, low: block-pos, high: block-pos, preload: bool) + -> bool; + level-remove-ticking-area: func(level: borrow, name: string) -> bool; + level-ticking-area-count: func(level: borrow) -> u32; + level-ticking-area-name: func(level: borrow, index: u32) -> option; } /// The server's scoreboard. A participant is an entity, a player through its entity id, or a fake player by name.