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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions java/src/main/java/falcon/api/CameraEase.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
95 changes: 95 additions & 0 deletions java/src/main/java/falcon/api/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item> 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<Entity> vehicle() {
return optional(api().entityVehicle(mHandle));
}

public List<Entity> passengers() {
int count = api().entityPassengerCount(mHandle);
List<Entity> 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<String> tags() {
int count = api().entityTagCount(mHandle);
List<String> 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<Entity> 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<Entity> optional(MemorySegment entity) {
return Interop.isNull(entity) ? Optional.empty() : Optional.of(new Entity(mContext, entity));
}

public Optional<Player> asPlayer() {
MemorySegment player = api().entityPlayer(mHandle);
if (Interop.isNull(player)) {
Expand Down
23 changes: 23 additions & 0 deletions java/src/main/java/falcon/api/EquipmentSlot.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
100 changes: 100 additions & 0 deletions java/src/main/java/falcon/api/Player.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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.Optional;

public final class Player {
private final PluginContext mContext;
Expand Down Expand Up @@ -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> 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.
*/
Expand Down
7 changes: 7 additions & 0 deletions java/src/main/java/falcon/api/SkinImage.java
Original file line number Diff line number Diff line change
@@ -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) {
}
4 changes: 4 additions & 0 deletions java/src/main/java/falcon/api/SpawnPoint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package falcon.api;

public record SpawnPoint(BlockPos position, Dimension dimension) {
}
Loading
Loading