Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,27 @@ for (const falcon::Score &score: falcon::Scoreboard::scores("kills"))
logger().info(score.name + ": " + std::to_string(score.value));
```

### Containers and signs

Any container block of a level, chests, barrels, furnaces, hoppers and the rest, is read and written by slot.
A player's ender chest is its own inventory, the same in every ender chest block. Players looking into a container
see the change at once:

```cpp
const falcon::BlockPos chest{10, 64, 10};
for (uint32_t slot = 0; slot < level.containerSize(chest); ++slot) {
if (std::optional<falcon::Item> item = level.containerItem(chest, slot))
logger().info(item->identifier());
}
level.setContainerItem(chest, 0, *falcon::Item::create("minecraft:diamond", 3));
player.inventory().setEnderChestItem(0, *falcon::Item::create("minecraft:elytra"));

level.setSignText({12, 64, 10}, "Welcome!");
```

Opening a container window that belongs to no block, such as a chest menu, is not part of the API yet: the server
has no block-less container window.

### C# plugins

The `dotnet` runtime is deprecated. Write new plugins for Wasm, native C++ or Java.
Expand Down
10 changes: 10 additions & 0 deletions java/src/main/java/falcon/api/Item.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public static Optional<Item> adopt(MemorySegment handle) {
return Optional.of(new Item(handle, true));
}

// an empty stack is freed at once: nobody would close it
static Optional<Item> adoptFilled(MemorySegment handle) {
Optional<Item> item = adopt(handle);
if (item.isPresent() && item.get().isEmpty()) {
item.get().close();
return Optional.empty();
}
return item;
}

public static Item borrow(MemorySegment handle) {
return new Item(handle == null ? MemorySegment.NULL : handle, false);
}
Expand Down
56 changes: 56 additions & 0 deletions java/src/main/java/falcon/api/Level.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,62 @@ public void spawnParticle(String particle, Vec3 position) {
}
}

/**
* The slot count of the chest, barrel, furnace, hopper or other container block there; 0 for none.
*/
public int containerSize(BlockPos position) {
try (Arena arena = Arena.ofConfined()) {
return api().levelContainerSize(mHandle, Interop.blockPos(arena, position));
}
}

/**
* A copy of the item in a container slot; empty for an empty slot or no container.
*/
public Optional<Item> containerItem(BlockPos position, int slot) {
try (Arena arena = Arena.ofConfined()) {
return Item.adoptFilled(api().levelContainerItem(mHandle, Interop.blockPos(arena, position), slot));
}
}

public boolean setContainerItem(BlockPos position, int slot, Item item) {
try (Arena arena = Arena.ofConfined()) {
return api().levelSetContainerItem(mHandle, Interop.blockPos(arena, position), slot, item.handle()) != 0;
}
}

public boolean clearContainerItem(BlockPos position, int slot) {
try (Arena arena = Arena.ofConfined()) {
return api().levelSetContainerItem(mHandle, Interop.blockPos(arena, position), slot,
MemorySegment.NULL) != 0;
}
}

public Optional<String> signText(BlockPos position) {
return signText(position, SignSide.FRONT);
}

/**
* The text of a sign or hanging sign, raw or JSON as the game saved it; empty when no sign is there.
*/
public Optional<String> signText(BlockPos position, SignSide side) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment text = api().levelSignText(mHandle, Interop.blockPos(arena, position), side.value());
return Interop.isNull(text) ? Optional.empty() : Optional.of(Interop.string(text));
}
}

public boolean setSignText(BlockPos position, String text) {
return setSignText(position, text, SignSide.FRONT);
}

public boolean setSignText(BlockPos position, String text, SignSide side) {
try (Arena arena = Arena.ofConfined()) {
return api().levelSetSignText(mHandle, Interop.blockPos(arena, position), side.value(),
Interop.text(arena, text)) != 0;
}
}

public List<Entity> entities() {
int count = api().levelEntityCount(mHandle);
List<Entity> result = new ArrayList<>(count);
Expand Down
19 changes: 19 additions & 0 deletions java/src/main/java/falcon/api/PlayerInventory.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ public boolean give(Item item) {
return api().playerGiveItem(mPlayer, item.handle()) != 0;
}

public int enderChestSize() {
return api().playerEnderChestSize(mPlayer);
}

/**
* The player's own ender chest, the same in every ender chest block and saved with the player.
*/
public Optional<Item> enderChestItem(int slot) {
return Item.adoptFilled(api().playerEnderChestItem(mPlayer, slot));
}

public boolean setEnderChestItem(int slot, Item item) {
return api().playerSetEnderChestItem(mPlayer, slot, item.handle()) != 0;
}

public boolean clearEnderChestItem(int slot) {
return api().playerSetEnderChestItem(mPlayer, slot, MemorySegment.NULL) != 0;
}

public int selectedSlot() {
return api().playerSelectedSlot(mPlayer);
}
Expand Down
18 changes: 18 additions & 0 deletions java/src/main/java/falcon/api/SignSide.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package falcon.api;

import falcon.api.internal.FalconAbi;

public enum SignSide {
FRONT(FalconAbi.SIGN_FRONT),
BACK(FalconAbi.SIGN_BACK);

private final int mValue;

SignSide(int value) {
mValue = value;
}

public int value() {
return mValue;
}
}
39 changes: 39 additions & 0 deletions native/include/falcon/Level.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
namespace falcon {
class Entity;

enum class SignSide : uint32_t {
Front = FALCON_SIGN_FRONT,
Back = FALCON_SIGN_BACK
};

enum class Dimension : uint32_t {
Overworld = FALCON_DIMENSION_OVERWORLD,
Nether = FALCON_DIMENSION_NETHER,
Expand Down Expand Up @@ -117,6 +122,40 @@ namespace falcon {
detail::api().levelSpawnParticle(mHandle, particle.c_str(), position.raw());
}

/** The slot count of the chest, barrel, furnace, hopper or other container block there; 0 for none. */
uint32_t containerSize(const BlockPos &position) const {
return detail::api().levelContainerSize(mHandle, position.raw());
}

/** A copy of the item in a container slot; nothing for an empty slot or no container. */
std::optional<Item> containerItem(const BlockPos &position, uint32_t slot) const {
std::optional<Item> found = Item::adopt(detail::api().levelContainerItem(mHandle, position.raw(), slot));
if (found.has_value() && found->isEmpty())
return std::nullopt;
return found;
}

bool setContainerItem(const BlockPos &position, uint32_t slot, const Item &item) const {
return detail::api().levelSetContainerItem(mHandle, position.raw(), slot, item.handle()) != 0;
}

bool clearContainerItem(const BlockPos &position, uint32_t slot) const {
return detail::api().levelSetContainerItem(mHandle, position.raw(), slot, nullptr) != 0;
}

/** The text of a sign or hanging sign, raw or JSON as the game saved it; nothing when no sign is there. */
std::optional<std::string> signText(const BlockPos &position, SignSide side = SignSide::Front) const {
const char *text = detail::api().levelSignText(mHandle, position.raw(), static_cast<FalconSignSide>(side));
if (text == nullptr)
return std::nullopt;
return std::string(text);
}

bool setSignText(const BlockPos &position, const std::string &text, SignSide side = SignSide::Front) const {
return detail::api().levelSetSignText(mHandle, position.raw(), static_cast<FalconSignSide>(side),
text.c_str()) != 0;
}

std::vector<Entity> entities() const;

FalconLevel *handle() const {
Expand Down
20 changes: 20 additions & 0 deletions native/include/falcon/PlayerInventory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ namespace falcon {
return detail::api().playerGiveItem(mPlayer, item.handle()) != 0;
}

uint32_t enderChestSize() const {
return detail::api().playerEnderChestSize(mPlayer);
}

/** The player's own ender chest, the same in every ender chest block and saved with the player. */
std::optional<Item> enderChestItem(uint32_t slot) const {
std::optional<Item> found = Item::adopt(detail::api().playerEnderChestItem(mPlayer, slot));
if (found.has_value() && found->isEmpty())
return std::nullopt;
return found;
}

bool setEnderChestItem(uint32_t slot, const Item &item) const {
return detail::api().playerSetEnderChestItem(mPlayer, slot, item.handle()) != 0;
}

bool clearEnderChestItem(uint32_t slot) const {
return detail::api().playerSetEnderChestItem(mPlayer, slot, nullptr) != 0;
}

uint32_t selectedSlot() const {
return detail::api().playerSelectedSlot(mPlayer);
}
Expand Down
13 changes: 13 additions & 0 deletions native/include/falcon/falcon_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ typedef uint32_t FalconDisplaySlot;
#define FALCON_DISPLAY_SLOT_LIST 1u
#define FALCON_DISPLAY_SLOT_BELOW_NAME 2u

typedef uint32_t FalconSignSide;
#define FALCON_SIGN_FRONT 0u
#define FALCON_SIGN_BACK 1u

typedef uint32_t FalconLogLevel;
#define FALCON_LOG_INFO 0u
#define FALCON_LOG_WARNING 1u
Expand Down Expand Up @@ -552,6 +556,15 @@ typedef struct FalconServerApi {
FalconEntity *(*scoreboardScoreEntity)(const char *objective, uint32_t index);
int (*scoreboardSetDisplay)(FalconDisplaySlot slot, const char *objective, int ascending);
const char *(*scoreboardDisplayObjective)(FalconDisplaySlot slot);

uint32_t (*levelContainerSize)(FalconLevel *level, FalconBlockPos position);
FalconItem *(*levelContainerItem)(FalconLevel *level, FalconBlockPos position, uint32_t slot);
int (*levelSetContainerItem)(FalconLevel *level, FalconBlockPos position, uint32_t slot, FalconItem *item);
const char *(*levelSignText)(FalconLevel *level, FalconBlockPos position, FalconSignSide side);
int (*levelSetSignText)(FalconLevel *level, FalconBlockPos position, FalconSignSide side, const char *text);
uint32_t (*playerEnderChestSize)(FalconPlayer *player);
FalconItem *(*playerEnderChestItem)(FalconPlayer *player, uint32_t slot);
int (*playerSetEnderChestItem)(FalconPlayer *player, uint32_t slot, FalconItem *item);
} FalconServerApi;

typedef int (*FalconPluginEntry)(const FalconServerApi *api, FalconPlugin *plugin, FalconPluginCallbacks *callbacks);
Expand Down
12 changes: 12 additions & 0 deletions wasm/falcon.wit
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@ interface levels {
server-unload-world: func(name: string) -> bool;
level-play-sound: func(level: borrow<level>, sound: string, position: vec3, volume: f32, pitch: f32);
level-spawn-particle: func(level: borrow<level>, particle: string, position: vec3);
level-container-size: func(level: borrow<level>, position: block-pos) -> u32;
/// A copy of the item in the slot; `none` for an empty slot or no container.
level-container-item: func(level: borrow<level>, position: block-pos, slot: u32) -> option<item>;
/// `none` empties the slot.
level-set-container-item: func(level: borrow<level>, position: block-pos, slot: u32, item: option<borrow<item>>)
-> bool;
/// `side`: `FALCON_SIGN_FRONT` or `FALCON_SIGN_BACK`.
level-sign-text: func(level: borrow<level>, position: block-pos, side: u32) -> option<string>;
level-set-sign-text: func(level: borrow<level>, position: block-pos, side: u32, text: string) -> bool;
}

/// The server's scoreboard. A participant is an entity, a player through its entity id, or a fake player by name.
Expand Down Expand Up @@ -283,6 +292,9 @@ interface items {
player-offhand-item: func(player: player) -> option<item>;
player-set-offhand-item: func(player: player, item: option<borrow<item>>);
player-clear-inventory: func(player: player);
player-ender-chest-size: func(player: player) -> u32;
player-ender-chest-item: func(player: player, slot: u32) -> option<item>;
player-set-ender-chest-item: func(player: player, slot: u32, item: option<borrow<item>>) -> bool;
}

interface events {
Expand Down
Loading