chore(plugin-api): extend entity api

This commit is contained in:
Alexander Medvedev 2026-05-17 19:46:57 +02:00
parent 7467195821
commit 4a4b72c83c
23 changed files with 1207 additions and 171 deletions

7
Cargo.lock generated
View File

@ -2988,7 +2988,6 @@ dependencies = [
"pumpkin-util", "pumpkin-util",
"pumpkin-world", "pumpkin-world",
"serde", "serde",
"take_mut",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio", "tokio",
"uuid", "uuid",
@ -3760,12 +3759,6 @@ dependencies = [
"winx", "winx",
] ]
[[package]]
name = "take_mut"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.13.5" version = "0.13.5"

View File

@ -169,7 +169,6 @@ tracing-subscriber = { version = "0.3.23", features = [
"time", "time",
] } ] }
slotmap = "1.1" slotmap = "1.1"
take_mut = "0.2.2"
temp-dir = "0.2.0" temp-dir = "0.2.0"
thread_local = "1.1.9" thread_local = "1.1.9"
time = "0.3" time = "0.3"

View File

@ -1,22 +1,12 @@
interface block-entity { interface block-entity {
use common.{block-position}; use common.{block-pos};
resource block-entity { resource block-entity {
resource-location: func() -> string; resource-location: func() -> string;
get-position: func() -> block-position; get-position: func() -> block-pos;
get-id: func() -> u32; get-id: func() -> u32;
// write-nbt: func(nbt: nbt-compound);
// from-nbt: func(nbt: nbt-compound, position: block-position) -> entity;
// tick: func(world: simple-world);
// write-internal: func(nbt: nbt-compound);
// chunk-data-nbt: func() -> option<nbt-compound;
// get-inventory: func() -> option<inventory>;
// set-block-state: func(block-state: block-state-id);
// on-block-replaced: func(world: simple-world, position: block-position);
is-dirty: func() -> bool; is-dirty: func() -> bool;
clear-dirty: func(); clear-dirty: func();
// to-property-delegate: func() -> option<property-delegate>;
// to-experience-container: func() -> option<experience-container>;
} }
resource command-block-entity { resource command-block-entity {
@ -30,7 +20,65 @@ interface block-entity {
powered: func() -> bool; powered: func() -> bool;
} }
enum dye-color {
white,
orange,
magenta,
light-blue,
yellow,
lime,
pink,
gray,
light-gray,
cyan,
purple,
blue,
brown,
green,
red,
black,
}
record sign-text {
messages: list<string>,
color: dye-color,
has-glowing-text: bool,
}
resource sign-block-entity {
get-block-entity: func() -> block-entity;
get-front-text: func() -> sign-text;
set-front-text: func(text: sign-text);
get-back-text: func() -> sign-text;
set-back-text: func(text: sign-text);
is-waxed: func() -> bool;
set-waxed: func(waxed: bool);
}
resource jukebox-block-entity {
get-block-entity: func() -> block-entity;
is-playing: func() -> bool;
stop-playing: func();
start-playing: func(length-in-ticks: u64);
}
resource chest-block-entity {
get-block-entity: func() -> block-entity;
viewer-count: func() -> u32;
}
resource mob-spawner-block-entity {
get-block-entity: func() -> block-entity;
get-spawn-count: func() -> s32;
get-spawn-range: func() -> s32;
get-delay: func() -> s32;
}
variant block-entity-type { variant block-entity-type {
command-block-entity(command-block-entity), command-block-entity(command-block-entity),
sign-block-entity(sign-block-entity),
jukebox-block-entity(jukebox-block-entity),
chest-block-entity(chest-block-entity),
mob-spawner-block-entity(mob-spawner-block-entity),
} }
} }

View File

@ -2,7 +2,7 @@ interface command {
use %world.{%world, entity}; use %world.{%world, entity};
use player.{player}; use player.{player};
use block-entity.{command-block-entity}; use block-entity.{command-block-entity};
use common.{position, block-position, locale, game-mode}; use common.{position, block-pos, locale, game-mode};
use item-stack.{item-stack}; use item-stack.{item-stack};
use server.{server, difficulty}; use server.{server, difficulty};
use text.{text-component}; use text.{text-component};
@ -145,7 +145,7 @@ interface command {
block-predicate(string), block-predicate(string),
time(s32), time(s32),
num(result<number, not-in-bounds>), num(result<number, not-in-bounds>),
block-pos(tuple<s32, s32, s32>), block-pos(block-pos),
pos3d(tuple<f64, f64, f64>), pos3d(tuple<f64, f64, f64>),
pos2d(tuple<f64, f64>), pos2d(tuple<f64, f64>),
rotation(tuple<f32, bool, f32, bool>), rotation(tuple<f32, bool, f32, bool>),

View File

@ -5,7 +5,11 @@ interface common {
type raw-text-component = list<u8>; type raw-text-component = list<u8>;
/// A position in the world grid (integers). /// A position in the world grid (integers).
type block-position = tuple<s32, s32, s32>; record block-pos {
x: s32,
y: s32,
z: s32,
}
/// A precise coordinate in the world (floats). /// A precise coordinate in the world (floats).
type position = tuple<f64, f64, f64>; type position = tuple<f64, f64, f64>;

View File

@ -1,5 +1,5 @@
interface event { interface event {
use common.{block-position, game-mode, hand, position, click-type}; use common.{block-pos, game-mode, hand, position, click-type};
use item-stack.{item-stack}; use item-stack.{item-stack};
use player.{player}; use player.{player};
use text.{text-component}; use text.{text-component};
@ -163,7 +163,7 @@ interface event {
record player-interact-event-data { record player-interact-event-data {
player: player, player: player,
action: interact-action, action: interact-action,
clicked-pos: option<block-position>, clicked-pos: option<block-pos>,
block: string, block: string,
cancelled: bool cancelled: bool
} }
@ -213,7 +213,7 @@ interface event {
record block-redstone-event-data { record block-redstone-event-data {
target-world: %world, target-world: %world,
state-id: u16, state-id: u16,
block-position: block-position, block-pos: block-pos,
old-current: s32, old-current: s32,
new-current: s32, new-current: s32,
cancelled: bool cancelled: bool
@ -222,7 +222,7 @@ interface event {
record block-break-event-data { record block-break-event-data {
player: option<player>, player: option<player>,
block: string, block: string,
block-position: block-position, block-pos: block-pos,
exp: u32, exp: u32,
should-drop: bool, should-drop: bool,
cancelled: bool cancelled: bool
@ -248,7 +248,7 @@ interface event {
old-state-id: u16, old-state-id: u16,
new-block: string, new-block: string,
new-state-id: u16, new-state-id: u16,
block-position: block-position, block-pos: block-pos,
cancelled: bool cancelled: bool
} }
@ -256,7 +256,7 @@ interface event {
player: player, player: player,
block-placed: string, block-placed: string,
block-placed-against: string, block-placed-against: string,
block-position: block-position, block-pos: block-pos,
can-build: bool, can-build: bool,
cancelled: bool cancelled: bool
} }
@ -280,10 +280,10 @@ interface event {
record spawn-change-event-data { record spawn-change-event-data {
target-world: %world, target-world: %world,
previous-position: block-position, previous-position: block-pos,
previous-yaw: f32, previous-yaw: f32,
previous-pitch: f32, previous-pitch: f32,
new-position: block-position, new-position: block-pos,
new-yaw: f32, new-yaw: f32,
new-pitch: f32 new-pitch: f32
} }

View File

@ -1,5 +1,6 @@
interface %world { interface %world {
use common.{position, entity-pose}; use common.{position, entity-pose, block-pos};
use block-entity.{block-entity, command-block-entity, block-entity-type};
use text.{text-component}; use text.{text-component};
use scoreboard.{scoreboard}; use scoreboard.{scoreboard};
use particles.{particle}; use particles.{particle};
@ -99,13 +100,6 @@ interface %world {
id: u16, id: u16,
} }
/// A position in the world grid.
record block-pos {
x: s32,
y: s32,
z: s32,
}
resource entity { resource entity {
get-id: func() -> u32; get-id: func() -> u32;
get-uuid: func() -> uuid; get-uuid: func() -> uuid;
@ -118,29 +112,6 @@ interface %world {
get-pitch: func() -> f32; get-pitch: func() -> f32;
get-head-yaw: func() -> f32; get-head-yaw: func() -> f32;
is-on-ground: func() -> bool;
is-sneaking: func() -> bool;
set-sneaking: func(sneaking: bool);
is-sprinting: func() -> bool;
set-sprinting: func(sprinting: bool);
is-swimming: func() -> bool;
set-swimming: func(swimming: bool);
is-invisible: func() -> bool;
set-invisible: func(invisible: bool);
is-glowing: func() -> bool;
set-glowing: func(glowing: bool);
is-fall-flying: func() -> bool;
set-fall-flying: func(fall-flying: bool);
is-on-fire: func() -> bool;
set-on-fire: func(on-fire: bool);
teleport: func(pos: position, world-ref: %world); teleport: func(pos: position, world-ref: %world);
set-velocity: func(velocity: position); set-velocity: func(velocity: position);
get-velocity: func() -> position; get-velocity: func() -> position;
@ -158,6 +129,73 @@ interface %world {
get-fire-ticks: func() -> s32; get-fire-ticks: func() -> s32;
set-fire-ticks: func(ticks: s32); set-fire-ticks: func(ticks: s32);
get-health: func() -> f32;
set-health: func(health: f32);
get-max-health: func() -> f32;
damage: func(amount: f32);
is-dead: func() -> bool;
get-absorption: func() -> f32;
set-absorption: func(amount: f32);
get-age: func() -> s32;
set-age: func(age: s32);
get-fall-distance: func() -> f32;
set-fall-distance: func(distance: f32);
get-ticks-lived: func() -> s32;
set-ticks-lived: func(ticks: s32);
is-sneaking: func() -> bool;
set-sneaking: func(sneaking: bool);
is-sprinting: func() -> bool;
set-sprinting: func(sprinting: bool);
is-swimming: func() -> bool;
set-swimming: func(swimming: bool);
is-invisible: func() -> bool;
set-invisible: func(invisible: bool);
is-glowing: func() -> bool;
set-glowing: func(glowing: bool);
is-fall-flying: func() -> bool;
set-fall-flying: func(fall-flying: bool);
get-width: func() -> f32;
get-height: func() -> f32;
set-rotation: func(yaw: f32, pitch: f32);
is-on-ground: func() -> bool;
is-on-fire: func() -> bool;
set-on-fire: func(on-fire: bool);
has-visual-fire: func() -> bool;
set-visual-fire: func(visual-fire: bool);
get-portal-cooldown: func() -> u32;
set-portal-cooldown: func(cooldown: u32);
get-remaining-air: func() -> s32;
set-remaining-air: func(air: s32);
get-max-air: func() -> s32;
send-system-message: func(message: text-component);
get-eye-height: func() -> f32;
get-eye-position: func() -> position;
get-nearby-entities: func(x: f64, y: f64, z: f64) -> list<entity>;
get-vehicle: func() -> option<entity>;
get-passengers: func() -> list<entity>;
add-passenger: func(passenger: entity);
remove-passenger: func(passenger: entity);
eject-passengers: func();
get-bounding-box: func() -> bounding-box;
is-in-water: func() -> bool;
is-in-lava: func() -> bool;
remove: func(); remove: func();
/// Performs a raycast from the entity's eye position in its looking direction. /// Performs a raycast from the entity's eye position in its looking direction.
@ -172,6 +210,12 @@ interface %world {
face: block-direction, face: block-direction,
} }
/// Represents an axis-aligned bounding box.
record bounding-box {
min: position,
max: position,
}
/// Represents a cardinal direction or block face. /// Represents a cardinal direction or block face.
enum block-direction { enum block-direction {
down, down,
@ -267,5 +311,8 @@ interface %world {
/// Returns a list of all entities in this world. /// Returns a list of all entities in this world.
get-entities: func() -> list<entity>; get-entities: func() -> list<entity>;
/// Returns the block entity at the specified position, if any.
get-block-entity: func(pos: block-pos) -> option<block-entity-type>;
} }
} }

View File

@ -33,7 +33,6 @@ hybrid-array = "0.4"
async-compression = { workspace = true, features = ["tokio", "zlib", "deflate"] } async-compression = { workspace = true, features = ["tokio", "zlib", "deflate"] }
flate2.workspace = true flate2.workspace = true
take_mut.workspace = true
bitflags.workspace = true bitflags.workspace = true
[dev-dependencies] [dev-dependencies]

View File

@ -82,11 +82,6 @@ impl UDPNetworkDecoder {
&mut self, &mut self,
_key: &[u8; 16], _key: &[u8; 16],
) -> Result<(), EncryptionAlreadyEnabledError> { ) -> Result<(), EncryptionAlreadyEnabledError> {
// if matches!(self.reader, DecryptionReader::Decrypt(_)) {
// return Err(EncryptionAlreadyEnabledError);
// }
// let cipher = Aes128Cfb8Dec::new_from_slices(key, key).map_err(|_| ());
// take_mut::take(&mut self.reader, |decoder| decoder.upgrade(cipher));
Ok(()) Ok(())
} }

View File

@ -107,11 +107,6 @@ impl UDPNetworkEncoder {
&mut self, &mut self,
_key: &[u8; 16], _key: &[u8; 16],
) -> Result<(), crate::bedrock::packet_decoder::EncryptionAlreadyEnabledError> { ) -> Result<(), crate::bedrock::packet_decoder::EncryptionAlreadyEnabledError> {
// if matches!(self.writer, EncryptionWriter::Encrypt(_)) {
// return Err(crate::bedrock::packet_decoder::EncryptionAlreadyEnabledError);
// }
// let cipher = Aes128Cfb8Enc::new_from_slices(key, key).map_err(|_| ());
// take_mut::take(&mut self.writer, |encoder| encoder.upgrade(cipher));
Ok(()) Ok(())
} }

View File

@ -74,7 +74,7 @@ impl<R: AsyncRead + Unpin> AsyncRead for DecryptionReader<R> {
/// Supports `ZLib` decoding/decompression /// Supports `ZLib` decoding/decompression
/// Supports Aes128 Encryption /// Supports Aes128 Encryption
pub struct TCPNetworkDecoder<R: AsyncRead + Unpin> { pub struct TCPNetworkDecoder<R: AsyncRead + Unpin> {
reader: DecryptionReader<R>, reader: Option<DecryptionReader<R>>,
compression: Option<CompressionThreshold>, compression: Option<CompressionThreshold>,
payload_scratch: BytesMut, payload_scratch: BytesMut,
} }
@ -82,7 +82,7 @@ pub struct TCPNetworkDecoder<R: AsyncRead + Unpin> {
impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> { impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> {
pub fn new(reader: R) -> Self { pub fn new(reader: R) -> Self {
Self { Self {
reader: DecryptionReader::None(reader), reader: Some(DecryptionReader::None(reader)),
compression: None, compression: None,
payload_scratch: BytesMut::new(), payload_scratch: BytesMut::new(),
} }
@ -94,19 +94,27 @@ impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> {
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted /// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub fn set_encryption(&mut self, key: &[u8; 16]) -> Result<(), PacketDecodeError> { pub fn set_encryption(&mut self, key: &[u8; 16]) -> Result<(), PacketDecodeError> {
if matches!(self.reader, DecryptionReader::Decrypt(_)) { if matches!(self.reader, Some(DecryptionReader::Decrypt(_))) {
return Err(PacketDecodeError::Message( return Err(PacketDecodeError::Message(
"Encryption already enabled".into(), "Encryption already enabled".into(),
)); ));
} }
let cipher = Aes128Cfb8Dec::new_from_slices(key, key) let cipher = Aes128Cfb8Dec::new_from_slices(key, key)
.map_err(|_| PacketDecodeError::Message("Invalid key".into()))?; .map_err(|_| PacketDecodeError::Message("Invalid key".into()))?;
take_mut::take(&mut self.reader, |decoder| decoder.upgrade(cipher));
if let Some(reader) = self.reader.take() {
self.reader = Some(reader.upgrade(cipher));
}
Ok(()) Ok(())
} }
pub async fn get_raw_packet(&mut self) -> Result<RawPacket, PacketDecodeError> { pub async fn get_raw_packet(&mut self) -> Result<RawPacket, PacketDecodeError> {
let packet_len = VarInt::decode_async(&mut self.reader) let reader = self
.reader
.as_mut()
.ok_or_else(|| PacketDecodeError::Message("Reader missing".into()))?;
let packet_len = VarInt::decode_async(reader)
.await .await
.map_err(|err| match err { .map_err(|err| match err {
ReadingError::CleanEOF(_) => PacketDecodeError::ConnectionClosed, ReadingError::CleanEOF(_) => PacketDecodeError::ConnectionClosed,
@ -119,7 +127,7 @@ impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> {
Err(PacketDecodeError::OutOfBounds)?; Err(PacketDecodeError::OutOfBounds)?;
} }
let mut bounded_reader = (&mut self.reader).take(packet_len); let mut bounded_reader = reader.take(packet_len);
let mut expected_packet_data_len = packet_len as usize; let mut expected_packet_data_len = packet_len as usize;
let mut expected_uncompressed_packet_data_len = None; let mut expected_uncompressed_packet_data_len = None;

View File

@ -81,7 +81,7 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for EncryptionWriter<W> {
/// Supports `ZLib` endecoding/compression /// Supports `ZLib` endecoding/compression
/// Supports Aes128 Encryption /// Supports Aes128 Encryption
pub struct TCPNetworkEncoder<W: AsyncWrite + Unpin> { pub struct TCPNetworkEncoder<W: AsyncWrite + Unpin> {
writer: EncryptionWriter<W>, writer: Option<EncryptionWriter<W>>,
// compression and compression threshold // compression and compression threshold
compression: Option<(CompressionThreshold, CompressionLevel)>, compression: Option<(CompressionThreshold, CompressionLevel)>,
// Reused compressor to avoid constructing zlib state per packet. // Reused compressor to avoid constructing zlib state per packet.
@ -93,7 +93,7 @@ pub struct TCPNetworkEncoder<W: AsyncWrite + Unpin> {
impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> { impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
pub const fn new(writer: W) -> Self { pub const fn new(writer: W) -> Self {
Self { Self {
writer: EncryptionWriter::None(writer), writer: Some(EncryptionWriter::None(writer)),
compression: None, compression: None,
compressor: None, compressor: None,
compression_scratch: Vec::new(), compression_scratch: Vec::new(),
@ -109,14 +109,17 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted /// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub fn set_encryption(&mut self, key: &[u8; 16]) -> Result<(), PacketEncodeError> { pub fn set_encryption(&mut self, key: &[u8; 16]) -> Result<(), PacketEncodeError> {
if matches!(self.writer, EncryptionWriter::Encrypt(_)) { if matches!(self.writer, Some(EncryptionWriter::Encrypt(_))) {
return Err(PacketEncodeError::Message( return Err(PacketEncodeError::Message(
"Encryption already enabled".into(), "Encryption already enabled".into(),
)); ));
} }
let cipher = Aes128Cfb8Enc::new_from_slices(key, key) let cipher = Aes128Cfb8Enc::new_from_slices(key, key)
.map_err(|_| PacketEncodeError::Message("Invalid key".into()))?; .map_err(|_| PacketEncodeError::Message("Invalid key".into()))?;
take_mut::take(&mut self.writer, |encoder| encoder.upgrade(cipher));
if let Some(writer) = self.writer.take() {
self.writer = Some(writer.upgrade(cipher));
}
Ok(()) Ok(())
} }
@ -242,14 +245,16 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
} }
full_packet_len_var_int full_packet_len_var_int
.encode_async(&mut self.writer) .encode_async(self.writer.as_mut().unwrap())
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
data_len_var_int data_len_var_int
.encode_async(&mut self.writer) .encode_async(self.writer.as_mut().unwrap())
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
self.writer self.writer
.as_mut()
.unwrap()
.write_all(&self.compression_scratch) .write_all(&self.compression_scratch)
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
@ -274,14 +279,16 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
} }
full_packet_len_var_int full_packet_len_var_int
.encode_async(&mut self.writer) .encode_async(self.writer.as_mut().unwrap())
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
data_len_var_int data_len_var_int
.encode_async(&mut self.writer) .encode_async(self.writer.as_mut().unwrap())
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
self.writer self.writer
.as_mut()
.unwrap()
.write_all(&packet_data) .write_all(&packet_data)
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
@ -299,10 +306,12 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
} }
full_packet_len_var_int full_packet_len_var_int
.encode_async(&mut self.writer) .encode_async(self.writer.as_mut().unwrap())
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
self.writer self.writer
.as_mut()
.unwrap()
.write_all(&packet_data) .write_all(&packet_data)
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string()))?; .map_err(|err| PacketEncodeError::Message(err.to_string()))?;
@ -313,6 +322,8 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
pub async fn flush(&mut self) -> Result<(), PacketEncodeError> { pub async fn flush(&mut self) -> Result<(), PacketEncodeError> {
self.writer self.writer
.as_mut()
.unwrap()
.flush() .flush()
.await .await
.map_err(|err| PacketEncodeError::Message(err.to_string())) .map_err(|err| PacketEncodeError::Message(err.to_string()))

View File

@ -11,7 +11,7 @@ use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[derive(Clone, Default)] #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
#[repr(i8)] #[repr(i8)]
pub enum DyeColor { pub enum DyeColor {
White = 0, White = 0,

View File

@ -1936,6 +1936,7 @@ impl JavaClient {
.await; .await;
} }
#[allow(clippy::too_many_lines)]
pub async fn handle_use_item_on( pub async fn handle_use_item_on(
&self, &self,
player: &Arc<Player>, player: &Arc<Player>,
@ -1985,6 +1986,26 @@ impl JavaClient {
let world = entity.world.load_full(); let world = entity.world.load_full();
let block = world.get_block(&position); let block = world.get_block(&position);
let event = PlayerInteractEvent::new(
player,
InteractAction::RightClickBlock,
block,
Some(position),
);
send_cancellable! {{
server;
event;
'cancelled: {
self.enqueue_packet(&CBlockUpdate::new(
position,
VarInt(block.id as i32),
))
.await;
return Ok(());
}
}}
let sneaking = player.living_entity.entity.sneaking.load(Ordering::Relaxed); let sneaking = player.living_entity.entity.sneaking.load(Ordering::Relaxed);
// Code based on the java class ServerPlayerInteractionManager // Code based on the java class ServerPlayerInteractionManager

View File

@ -60,10 +60,10 @@ impl PlayerInteractEvent {
/// Enum representing possible player interaction actions. /// Enum representing possible player interaction actions.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub enum InteractAction { pub enum InteractAction {
/// Left-clicking the air /// Left-clicking a block
LeftClickBlock, LeftClickBlock,
/// Left-clicking a block /// Left-clicking the air
LeftClickAir, LeftClickAir,
/// Right-clicking the air /// Right-clicking the air

View File

@ -55,6 +55,7 @@ pub type ConsumedArgsResource = WasmResource<OwnedConsumedArgs>;
pub type CommandNodeResource = WasmResource<NonLeafNodeBuilder>; pub type CommandNodeResource = WasmResource<NonLeafNodeBuilder>;
pub type ItemStackResource = WasmResource<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>>; pub type ItemStackResource = WasmResource<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>>;
pub type RecipeManagerResource = WasmResource<Arc<RecipeManager>>; pub type RecipeManagerResource = WasmResource<Arc<RecipeManager>>;
pub type BlockEntityResource = WasmResource<Arc<dyn crate::block::entities::BlockEntity>>;
pub type OwnedConsumedArgs = HashMap<String, OwnedArg>; pub type OwnedConsumedArgs = HashMap<String, OwnedArg>;
@ -240,6 +241,14 @@ impl PluginHostState {
.push(RecipeManagerResource { provider })?; .push(RecipeManagerResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep())) Ok(wasmtime::component::Resource::new_own(resource.rep()))
} }
pub fn add_block_entity<T>(
&mut self,
provider: Arc<dyn crate::block::entities::BlockEntity>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(BlockEntityResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
} }
pub struct PluginHttpHooks { pub struct PluginHttpHooks {

View File

@ -1,90 +1,539 @@
use std::sync::Arc;
use wasmtime::component::Resource; use wasmtime::component::Resource;
use crate::block::entities::BlockEntity as InternalBlockEntity;
use crate::block::entities::chest::ChestBlockEntity as InternalChestBlockEntity;
use crate::block::entities::command_block::CommandBlockEntity as InternalCommandBlockEntity;
use crate::block::entities::jukebox::JukeboxBlockEntity as InternalJukeboxBlockEntity;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity as InternalMobSpawnerBlockEntity;
use crate::block::entities::sign::{
DyeColor as InternalDyeColor, SignBlockEntity as InternalSignBlockEntity, Text as InternalText,
};
use crate::plugin::loader::wasm::wasm_host::{ use crate::plugin::loader::wasm::wasm_host::{
state::PluginHostState, state::{BlockEntityResource, PluginHostState},
wit::v0_1::pumpkin::{ wit::v0_1::pumpkin::{
self, self,
plugin::{ plugin::{
block_entity::{BlockEntity, CommandBlockEntity}, block_entity::{
common::BlockPosition, BlockEntity, ChestBlockEntity, CommandBlockEntity, DyeColor, HostBlockEntity,
HostChestBlockEntity, HostCommandBlockEntity, HostJukeboxBlockEntity,
HostMobSpawnerBlockEntity, HostSignBlockEntity, JukeboxBlockEntity,
MobSpawnerBlockEntity, SignBlockEntity, SignText,
},
common::BlockPos as WitBlockPos,
}, },
}, },
}; };
impl pumpkin::plugin::block_entity::Host for PluginHostState {} impl pumpkin::plugin::block_entity::Host for PluginHostState {}
impl pumpkin::plugin::block_entity::HostBlockEntity for PluginHostState { fn block_entity_from_resource(
async fn resource_location(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<String> { state: &PluginHostState,
todo!("Implement resource_location lookup") entity: &Resource<BlockEntity>,
} ) -> wasmtime::Result<Arc<dyn InternalBlockEntity>> {
state
.resource_table
.get::<BlockEntityResource>(&Resource::new_own(entity.rep()))
.map_err(|_| wasmtime::Error::msg("invalid block entity resource handle"))
.map(|resource| resource.provider.clone())
}
async fn get_position( const fn from_wasm_dye_color(color: DyeColor) -> InternalDyeColor {
&mut self, match color {
_res: Resource<BlockEntity>, DyeColor::White => InternalDyeColor::White,
) -> wasmtime::Result<BlockPosition> { DyeColor::Orange => InternalDyeColor::Orange,
todo!("Implement get_position lookup") DyeColor::Magenta => InternalDyeColor::Magenta,
} DyeColor::LightBlue => InternalDyeColor::LightBlue,
DyeColor::Yellow => InternalDyeColor::Yellow,
async fn get_id(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<u32> { DyeColor::Lime => InternalDyeColor::Lime,
todo!("Implement get_id lookup") DyeColor::Pink => InternalDyeColor::Pink,
} DyeColor::Gray => InternalDyeColor::Gray,
DyeColor::LightGray => InternalDyeColor::LightGray,
async fn is_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<bool> { DyeColor::Cyan => InternalDyeColor::Cyan,
todo!("Implement is_dirty lookup") DyeColor::Purple => InternalDyeColor::Purple,
} DyeColor::Blue => InternalDyeColor::Blue,
DyeColor::Brown => InternalDyeColor::Brown,
async fn clear_dirty(&mut self, _res: Resource<BlockEntity>) -> wasmtime::Result<()> { DyeColor::Green => InternalDyeColor::Green,
todo!("Implement clear_dirty logic") DyeColor::Red => InternalDyeColor::Red,
} DyeColor::Black => InternalDyeColor::Black,
async fn drop(&mut self, _rep: Resource<BlockEntity>) -> wasmtime::Result<()> {
todo!();
} }
} }
impl pumpkin::plugin::block_entity::HostCommandBlockEntity for PluginHostState { const fn to_wasm_dye_color(color: InternalDyeColor) -> DyeColor {
match color {
InternalDyeColor::White => DyeColor::White,
InternalDyeColor::Orange => DyeColor::Orange,
InternalDyeColor::Magenta => DyeColor::Magenta,
InternalDyeColor::LightBlue => DyeColor::LightBlue,
InternalDyeColor::Yellow => DyeColor::Yellow,
InternalDyeColor::Lime => DyeColor::Lime,
InternalDyeColor::Pink => DyeColor::Pink,
InternalDyeColor::Gray => DyeColor::Gray,
InternalDyeColor::LightGray => DyeColor::LightGray,
InternalDyeColor::Cyan => DyeColor::Cyan,
InternalDyeColor::Purple => DyeColor::Purple,
InternalDyeColor::Blue => DyeColor::Blue,
InternalDyeColor::Brown => DyeColor::Brown,
InternalDyeColor::Green => DyeColor::Green,
InternalDyeColor::Red => DyeColor::Red,
InternalDyeColor::Black => DyeColor::Black,
}
}
fn to_wasm_sign_text(text: &InternalText) -> SignText {
SignText {
messages: text.messages.lock().unwrap().to_vec(),
color: to_wasm_dye_color(text.get_color()),
has_glowing_text: text
.has_glowing_text
.load(std::sync::atomic::Ordering::Relaxed),
}
}
fn from_wasm_sign_text(text: SignText) -> InternalText {
let mut messages = [String::new(), String::new(), String::new(), String::new()];
for (i, msg) in text.messages.into_iter().take(4).enumerate() {
messages[i] = msg;
}
InternalText::from(pumpkin_nbt::tag::NbtTag::Compound({
let mut nbt = pumpkin_nbt::compound::NbtCompound::new();
nbt.put_bool("has_glowing_text", text.has_glowing_text);
nbt.put_string(
"color",
InternalDyeColor::from(from_wasm_dye_color(text.color) as i8).into(),
);
nbt.put_list(
"messages",
messages
.iter()
.map(|s| pumpkin_nbt::tag::NbtTag::String(s.clone().into()))
.collect(),
);
nbt
}))
}
impl HostBlockEntity for PluginHostState {
async fn resource_location(&mut self, res: Resource<BlockEntity>) -> wasmtime::Result<String> {
let entity = block_entity_from_resource(self, &res)?;
Ok(entity.resource_location().to_string())
}
async fn get_position(&mut self, res: Resource<BlockEntity>) -> wasmtime::Result<WitBlockPos> {
let entity = block_entity_from_resource(self, &res)?;
let pos = entity.get_position();
Ok(WitBlockPos {
x: pos.0.x,
y: pos.0.y,
z: pos.0.z,
})
}
async fn get_id(&mut self, res: Resource<BlockEntity>) -> wasmtime::Result<u32> {
let entity = block_entity_from_resource(self, &res)?;
Ok(entity.get_id())
}
async fn is_dirty(&mut self, res: Resource<BlockEntity>) -> wasmtime::Result<bool> {
let entity = block_entity_from_resource(self, &res)?;
Ok(entity.is_dirty())
}
async fn clear_dirty(&mut self, res: Resource<BlockEntity>) -> wasmtime::Result<()> {
let entity = block_entity_from_resource(self, &res)?;
entity.clear_dirty();
Ok(())
}
async fn drop(&mut self, rep: Resource<BlockEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<BlockEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostCommandBlockEntity for PluginHostState {
async fn get_block_entity( async fn get_block_entity(
&mut self, &mut self,
_res: Resource<CommandBlockEntity>, res: Resource<CommandBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> { ) -> wasmtime::Result<Resource<BlockEntity>> {
todo!("Implement upcasting/mapping to BlockEntity") let entity = self
.resource_table
.get::<BlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid command block entity resource handle"))?;
let provider = entity.provider.clone();
self.add_block_entity(provider)
} }
async fn last_output( async fn last_output(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<String> {
&mut self, let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
_res: Resource<CommandBlockEntity>, if let Some(cmd) = entity.as_any().downcast_ref::<InternalCommandBlockEntity>() {
) -> wasmtime::Result<String> { Ok(cmd.last_output.lock().await.clone())
todo!("Implement last_output lookup") } else {
Err(wasmtime::Error::msg("Not a command block entity"))
}
} }
async fn track_output(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> { async fn track_output(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement track_output lookup") let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalCommandBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a command block entity")),
|cmd| Ok(cmd.track_output.load(std::sync::atomic::Ordering::Relaxed)),
)
} }
async fn success_count(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<u32> { async fn success_count(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<u32> {
todo!("Implement success_count lookup") let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalCommandBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a command block entity")),
|cmd| Ok(cmd.success_count.load(std::sync::atomic::Ordering::Relaxed)),
)
} }
async fn command(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<String> { async fn command(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<String> {
todo!("Implement command lookup") let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
if let Some(cmd) = entity.as_any().downcast_ref::<InternalCommandBlockEntity>() {
Ok(cmd.command.lock().await.clone())
} else {
Err(wasmtime::Error::msg("Not a command block entity"))
}
} }
async fn auto(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> { async fn auto(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement auto lookup") let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalCommandBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a command block entity")),
|cmd| Ok(cmd.auto.load(std::sync::atomic::Ordering::Relaxed)),
)
} }
async fn condition_met( async fn condition_met(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
&mut self, let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
_res: Resource<CommandBlockEntity>, entity
) -> wasmtime::Result<bool> { .as_any()
todo!("Implement condition_met lookup") .downcast_ref::<InternalCommandBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a command block entity")),
|cmd| Ok(cmd.condition_met.load(std::sync::atomic::Ordering::Relaxed)),
)
} }
async fn powered(&mut self, _res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> { async fn powered(&mut self, res: Resource<CommandBlockEntity>) -> wasmtime::Result<bool> {
todo!("Implement powered lookup") let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalCommandBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a command block entity")),
|cmd| Ok(cmd.powered.load(std::sync::atomic::Ordering::Relaxed)),
)
} }
async fn drop(&mut self, _rep: Resource<CommandBlockEntity>) -> wasmtime::Result<()> { async fn drop(&mut self, rep: Resource<CommandBlockEntity>) -> wasmtime::Result<()> {
todo!(); let _ = self
.resource_table
.delete::<BlockEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostSignBlockEntity for PluginHostState {
async fn get_block_entity(
&mut self,
res: Resource<SignBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
let entity = self
.resource_table
.get::<BlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid sign block entity resource handle"))?;
let provider = entity.provider.clone();
self.add_block_entity(provider)
}
async fn get_front_text(
&mut self,
res: Resource<SignBlockEntity>,
) -> wasmtime::Result<SignText> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a sign block entity")),
|sign| Ok(to_wasm_sign_text(&sign.front_text)),
)
}
async fn set_front_text(
&mut self,
res: Resource<SignBlockEntity>,
text: SignText,
) -> wasmtime::Result<()> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a sign block entity")),
|sign| {
let new_text = from_wasm_sign_text(text);
sign.front_text.has_glowing_text.store(
new_text
.has_glowing_text
.load(std::sync::atomic::Ordering::Relaxed),
std::sync::atomic::Ordering::Relaxed,
);
sign.front_text.set_color(new_text.get_color());
(*sign.front_text.messages.lock().unwrap())
.clone_from(&new_text.messages.lock().unwrap());
Ok(())
},
)
}
async fn get_back_text(
&mut self,
res: Resource<SignBlockEntity>,
) -> wasmtime::Result<SignText> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a sign block entity")),
|sign| Ok(to_wasm_sign_text(&sign.back_text)),
)
}
async fn set_back_text(
&mut self,
res: Resource<SignBlockEntity>,
text: SignText,
) -> wasmtime::Result<()> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a sign block entity")),
|sign| {
let new_text = from_wasm_sign_text(text);
sign.back_text.has_glowing_text.store(
new_text
.has_glowing_text
.load(std::sync::atomic::Ordering::Relaxed),
std::sync::atomic::Ordering::Relaxed,
);
sign.back_text.set_color(new_text.get_color());
(*sign.back_text.messages.lock().unwrap())
.clone_from(&new_text.messages.lock().unwrap());
Ok(())
},
)
}
async fn is_waxed(&mut self, res: Resource<SignBlockEntity>) -> wasmtime::Result<bool> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a sign block entity")),
|sign| Ok(sign.is_waxed.load(std::sync::atomic::Ordering::Relaxed)),
)
}
async fn set_waxed(
&mut self,
res: Resource<SignBlockEntity>,
waxed: bool,
) -> wasmtime::Result<()> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a sign block entity")),
|sign| {
sign.is_waxed
.store(waxed, std::sync::atomic::Ordering::Relaxed);
Ok(())
},
)
}
async fn drop(&mut self, rep: Resource<SignBlockEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<BlockEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostJukeboxBlockEntity for PluginHostState {
async fn get_block_entity(
&mut self,
res: Resource<JukeboxBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
let entity = self
.resource_table
.get::<BlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid jukebox block entity resource handle"))?;
let provider = entity.provider.clone();
self.add_block_entity(provider)
}
async fn is_playing(&mut self, res: Resource<JukeboxBlockEntity>) -> wasmtime::Result<bool> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalJukeboxBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a jukebox block entity")),
|jukebox| Ok(jukebox.is_playing()),
)
}
async fn stop_playing(&mut self, res: Resource<JukeboxBlockEntity>) -> wasmtime::Result<()> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalJukeboxBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a jukebox block entity")),
|jukebox| {
jukebox.stop_playing();
Ok(())
},
)
}
async fn start_playing(
&mut self,
res: Resource<JukeboxBlockEntity>,
length_in_ticks: u64,
) -> wasmtime::Result<()> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalJukeboxBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a jukebox block entity")),
|jukebox| {
jukebox.start_playing(length_in_ticks);
Ok(())
},
)
}
async fn drop(&mut self, rep: Resource<JukeboxBlockEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<BlockEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostChestBlockEntity for PluginHostState {
async fn get_block_entity(
&mut self,
res: Resource<ChestBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
let entity = self
.resource_table
.get::<BlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| wasmtime::Error::msg("invalid chest block entity resource handle"))?;
let provider = entity.provider.clone();
self.add_block_entity(provider)
}
async fn viewer_count(&mut self, res: Resource<ChestBlockEntity>) -> wasmtime::Result<u32> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalChestBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a chest block entity")),
|chest| Ok(chest.get_viewer_count() as u32),
)
}
async fn drop(&mut self, rep: Resource<ChestBlockEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<BlockEntityResource>(Resource::new_own(rep.rep()));
Ok(())
}
}
impl HostMobSpawnerBlockEntity for PluginHostState {
async fn get_block_entity(
&mut self,
res: Resource<MobSpawnerBlockEntity>,
) -> wasmtime::Result<Resource<BlockEntity>> {
let entity = self
.resource_table
.get::<BlockEntityResource>(&Resource::new_own(res.rep()))
.map_err(|_| {
wasmtime::Error::msg("invalid mob spawner block entity resource handle")
})?;
let provider = entity.provider.clone();
self.add_block_entity(provider)
}
async fn get_spawn_count(
&mut self,
res: Resource<MobSpawnerBlockEntity>,
) -> wasmtime::Result<i32> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalMobSpawnerBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a mob spawner block entity")),
|spawner| Ok(spawner.spawn_count),
)
}
async fn get_spawn_range(
&mut self,
res: Resource<MobSpawnerBlockEntity>,
) -> wasmtime::Result<i32> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalMobSpawnerBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a mob spawner block entity")),
|spawner| Ok(spawner.spawn_range),
)
}
async fn get_delay(&mut self, res: Resource<MobSpawnerBlockEntity>) -> wasmtime::Result<i32> {
let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?;
entity
.as_any()
.downcast_ref::<InternalMobSpawnerBlockEntity>()
.map_or_else(
|| Err(wasmtime::Error::msg("Not a mob spawner block entity")),
|spawner| Ok(spawner.delay.load(std::sync::atomic::Ordering::Relaxed)),
)
}
async fn drop(&mut self, rep: Resource<MobSpawnerBlockEntity>) -> wasmtime::Result<()> {
let _ = self
.resource_table
.delete::<BlockEntityResource>(Resource::new_own(rep.rep()));
Ok(())
} }
} }

View File

@ -43,7 +43,7 @@ use crate::{
Arg, ArgumentType, Command, CommandNode, CommandSender, CommandSenderType, Arg, ArgumentType, Command, CommandNode, CommandSender, CommandSenderType,
ConsumedArgs, PermissionLevel, StringType, ConsumedArgs, PermissionLevel, StringType,
}, },
common::{Locale, Position}, common::{BlockPos as WitBlockPos, Locale, Position},
player::Player, player::Player,
server::Server, server::Server,
text::TextComponent, text::TextComponent,
@ -149,7 +149,11 @@ impl pumpkin::plugin::command::HostConsumedArgs for PluginHostState {
} }
})) }))
} }
OwnedArg::BlockPos(p) => Arg::BlockPos((p.0.x, p.0.y, p.0.z)), OwnedArg::BlockPos(p) => Arg::BlockPos(WitBlockPos {
x: p.0.x,
y: p.0.y,
z: p.0.z,
}),
OwnedArg::Pos3D(v) => Arg::Pos3d((v.x, v.y, v.z)), OwnedArg::Pos3D(v) => Arg::Pos3d((v.x, v.y, v.z)),
OwnedArg::Pos2D(v) => Arg::Pos2d((v.x, v.y)), OwnedArg::Pos2D(v) => Arg::Pos2d((v.x, v.y)),
OwnedArg::Rotation(a, b, c, d) => Arg::Rotation((a, b, c, d)), OwnedArg::Rotation(a, b, c, d) => Arg::Rotation((a, b, c, d)),

View File

@ -1,6 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use wasmtime::component::Resource; use wasmtime::component::Resource;
use pumpkin_util::math::vector3::Vector3;
use crate::plugin::loader::wasm::wasm_host::{ use crate::plugin::loader::wasm::wasm_host::{
state::{EntityResource, PluginHostState}, state::{EntityResource, PluginHostState},
wit::v0_1::events::to_wasm_position, wit::v0_1::events::to_wasm_position,
@ -11,7 +13,8 @@ use crate::plugin::loader::wasm::wasm_host::{
text::TextComponent, text::TextComponent,
uuid::Uuid, uuid::Uuid,
world::{ world::{
BlockPos as WitBlockPos, Entity, HostEntity, RaycastResult as WitRaycastResult, World, BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, HostEntity,
RaycastResult as WitRaycastResult, World,
}, },
}, },
wit::v0_1::uuid::UuidExt, wit::v0_1::uuid::UuidExt,
@ -283,8 +286,9 @@ impl HostEntity for PluginHostState {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
Ok(entity Ok(entity
.get_entity() .get_entity()
.has_visual_fire .fire_ticks
.load(std::sync::atomic::Ordering::Relaxed)) .load(std::sync::atomic::Ordering::Relaxed)
> 0)
} }
async fn set_on_fire( async fn set_on_fire(
@ -410,6 +414,370 @@ impl HostEntity for PluginHostState {
Ok(()) Ok(())
} }
async fn get_health(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.health.load()))
}
async fn set_health(&mut self, entity: Resource<Entity>, health: f32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.health.store(health);
}
Ok(())
}
async fn get_max_health(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, crate::entity::living::LivingEntity::get_max_health))
}
async fn damage(&mut self, entity: Resource<Entity>, amount: f32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity
.damage(&*entity, amount, pumpkin_data::damage::DamageType::GENERIC)
.await;
Ok(())
}
async fn is_dead(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_living_entity().map_or_else(
|| entity.get_entity().removal_reason.load().is_some(),
|living| living.dead.load(std::sync::atomic::Ordering::Relaxed),
))
}
async fn get_absorption(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.absorption.load()))
}
async fn set_absorption(
&mut self,
entity: Resource<Entity>,
amount: f32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.absorption.store(amount);
}
Ok(())
}
async fn get_age(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.age
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn set_age(&mut self, entity: Resource<Entity>, age: i32) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity
.get_entity()
.age
.store(age, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
async fn get_fall_distance(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_living_entity()
.map_or(0.0, |living| living.fall_distance.load()))
}
async fn set_fall_distance(
&mut self,
entity: Resource<Entity>,
distance: f32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(living) = entity.get_living_entity() {
living.fall_distance.store(distance);
}
Ok(())
}
async fn get_eye_height(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_dimension.load().eye_height)
}
async fn get_eye_position(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Position> {
let entity = entity_from_resource(self, &entity)?;
Ok(to_wasm_position(entity.get_eye_pos()))
}
async fn get_nearby_entities(
&mut self,
entity: Resource<Entity>,
x: f64,
y: f64,
z: f64,
) -> wasmtime::Result<Vec<Resource<Entity>>> {
let entity = entity_from_resource(self, &entity)?;
let pos = entity.get_entity().pos.load();
let box_range = pumpkin_util::math::boundingbox::BoundingBox::new(
Vector3::new(pos.x - x, pos.y - y, pos.z - z),
Vector3::new(pos.x + x, pos.y + y, pos.z + z),
);
let world = entity.get_entity().world.load_full();
let entities = world.get_entities_at_box(&box_range);
let mut result = Vec::new();
for e in entities {
// Don't include the entity itself
if e.get_entity().entity_id != entity.get_entity().entity_id {
result.push(
self.add_entity(e)
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))?,
);
}
}
Ok(result)
}
async fn get_vehicle(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<Option<Resource<Entity>>> {
let entity = entity_from_resource(self, &entity)?;
let vehicle = entity.get_entity().vehicle.lock().await;
if let Some(v) = vehicle.as_ref() {
Ok(Some(self.add_entity(Arc::clone(v)).map_err(|_| {
wasmtime::Error::msg("failed to add entity resource")
})?))
} else {
Ok(None)
}
}
async fn get_passengers(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<Vec<Resource<Entity>>> {
let entity = entity_from_resource(self, &entity)?;
let passengers = entity.get_entity().passengers.lock().await;
let mut result = Vec::new();
for p in passengers.iter() {
result.push(
self.add_entity(Arc::clone(p))
.map_err(|_| wasmtime::Error::msg("failed to add entity resource"))?,
);
}
Ok(result)
}
async fn add_passenger(
&mut self,
entity: Resource<Entity>,
passenger: Resource<Entity>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let passenger = entity_from_resource(self, &passenger)?;
entity
.get_entity()
.add_passenger(Arc::clone(&entity), passenger)
.await;
Ok(())
}
async fn remove_passenger(
&mut self,
entity: Resource<Entity>,
passenger: Resource<Entity>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let passenger = entity_from_resource(self, &passenger)?;
entity
.get_entity()
.remove_passenger(passenger.get_entity().entity_id)
.await;
Ok(())
}
async fn eject_passengers(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
let ids: Vec<i32> = entity
.get_entity()
.passengers
.lock()
.await
.iter()
.map(|p| p.get_entity().entity_id)
.collect();
for id in ids {
entity.get_entity().remove_passenger(id).await;
}
Ok(())
}
async fn get_bounding_box(
&mut self,
entity: Resource<Entity>,
) -> wasmtime::Result<WitBoundingBox> {
let entity = entity_from_resource(self, &entity)?;
let bb = entity.get_entity().bounding_box.load();
Ok(WitBoundingBox {
min: to_wasm_position(bb.min),
max: to_wasm_position(bb.max),
})
}
async fn is_in_water(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.touching_water
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn is_in_lava(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.touching_lava
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn get_ticks_lived(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.age
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn set_ticks_lived(
&mut self,
entity: Resource<Entity>,
ticks: i32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity
.get_entity()
.age
.store(ticks, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
async fn get_width(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_dimension.load().width)
}
async fn get_height(&mut self, entity: Resource<Entity>) -> wasmtime::Result<f32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_entity().entity_dimension.load().height)
}
async fn set_rotation(
&mut self,
entity: Resource<Entity>,
yaw: f32,
pitch: f32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.get_entity().set_rotation(yaw, pitch);
Ok(())
}
async fn has_visual_fire(&mut self, entity: Resource<Entity>) -> wasmtime::Result<bool> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.has_visual_fire
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn set_visual_fire(
&mut self,
entity: Resource<Entity>,
visual_fire: bool,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity.get_entity().set_on_fire(visual_fire).await;
Ok(())
}
async fn get_portal_cooldown(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity
.get_entity()
.portal_cooldown
.load(std::sync::atomic::Ordering::Relaxed))
}
async fn set_portal_cooldown(
&mut self,
entity: Resource<Entity>,
cooldown: u32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
entity
.get_entity()
.portal_cooldown
.store(cooldown, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
async fn get_remaining_air(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
let entity = entity_from_resource(self, &entity)?;
Ok(entity.get_player().map_or(0, |player| {
player
.breath_manager
.air_supply
.load(std::sync::atomic::Ordering::Relaxed)
}))
}
async fn set_remaining_air(
&mut self,
entity: Resource<Entity>,
air: i32,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(player) = entity.get_player() {
player
.breath_manager
.air_supply
.store(air, std::sync::atomic::Ordering::Relaxed);
player.breath_manager.send_air_supply(player);
}
Ok(())
}
async fn get_max_air(&mut self, _entity: Resource<Entity>) -> wasmtime::Result<i32> {
Ok(crate::entity::breath::MAX_AIR)
}
async fn send_system_message(
&mut self,
entity: Resource<Entity>,
message: Resource<TextComponent>,
) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?;
if let Some(player) = entity.get_player() {
let text_res = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::TextComponentResource>(
&Resource::new_own(message.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid text component resource handle"))?;
player.send_system_message(&text_res.provider).await;
}
Ok(())
}
async fn remove(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> { async fn remove(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
entity.get_entity().remove().await; entity.get_entity().remove().await;

View File

@ -28,7 +28,7 @@ impl ToFromWasmEvent for BlockRedstoneEvent {
Event::BlockRedstoneEvent(BlockRedstoneEventData { Event::BlockRedstoneEvent(BlockRedstoneEventData {
target_world, target_world,
state_id: self.block_state_id, state_id: self.block_state_id,
block_position: to_wasm_block_position(self.block_pos), block_pos: to_wasm_block_position(self.block_pos),
old_current: self.old_current, old_current: self.old_current,
new_current: self.new_current, new_current: self.new_current,
cancelled: self.cancelled, cancelled: self.cancelled,
@ -40,7 +40,7 @@ impl ToFromWasmEvent for BlockRedstoneEvent {
Event::BlockRedstoneEvent(data) => Self { Event::BlockRedstoneEvent(data) => Self {
world: consume_world(state, &data.target_world), world: consume_world(state, &data.target_world),
block_state_id: data.state_id, block_state_id: data.state_id,
block_pos: from_wasm_block_position(data.block_position), block_pos: from_wasm_block_position(data.block_pos),
old_current: data.old_current, old_current: data.old_current,
new_current: data.new_current, new_current: data.new_current,
cancelled: data.cancelled, cancelled: data.cancelled,
@ -61,7 +61,7 @@ impl ToFromWasmEvent for BlockBreakEvent {
Event::BlockBreakEvent(BlockBreakEventData { Event::BlockBreakEvent(BlockBreakEventData {
player, player,
block: to_wasm_block_name(self.block), block: to_wasm_block_name(self.block),
block_position: to_wasm_block_position(self.block_position), block_pos: to_wasm_block_position(self.block_position),
exp: self.exp, exp: self.exp,
should_drop: self.drop, should_drop: self.drop,
cancelled: self.cancelled, cancelled: self.cancelled,
@ -73,7 +73,7 @@ impl ToFromWasmEvent for BlockBreakEvent {
Event::BlockBreakEvent(data) => Self { Event::BlockBreakEvent(data) => Self {
player: data.player.map(|player| consume_player(state, &player)), player: data.player.map(|player| consume_player(state, &player)),
block: from_wasm_block_name(&data.block), block: from_wasm_block_name(&data.block),
block_position: from_wasm_block_position(data.block_position), block_position: from_wasm_block_position(data.block_pos),
exp: data.exp, exp: data.exp,
drop: data.should_drop, drop: data.should_drop,
cancelled: data.cancelled, cancelled: data.cancelled,
@ -145,7 +145,7 @@ impl ToFromWasmEvent for BlockGrowEvent {
old_state_id: self.old_state_id, old_state_id: self.old_state_id,
new_block: to_wasm_block_name(self.new_block), new_block: to_wasm_block_name(self.new_block),
new_state_id: self.new_state_id, new_state_id: self.new_state_id,
block_position: to_wasm_block_position(self.block_pos), block_pos: to_wasm_block_position(self.block_pos),
cancelled: self.cancelled, cancelled: self.cancelled,
}) })
} }
@ -158,7 +158,7 @@ impl ToFromWasmEvent for BlockGrowEvent {
old_state_id: data.old_state_id, old_state_id: data.old_state_id,
new_block: from_wasm_block_name(&data.new_block), new_block: from_wasm_block_name(&data.new_block),
new_state_id: data.new_state_id, new_state_id: data.new_state_id,
block_pos: from_wasm_block_position(data.block_position), block_pos: from_wasm_block_position(data.block_pos),
cancelled: data.cancelled, cancelled: data.cancelled,
}, },
_ => panic!("unexpected event type"), _ => panic!("unexpected event type"),
@ -176,7 +176,7 @@ impl ToFromWasmEvent for BlockPlaceEvent {
player, player,
block_placed: to_wasm_block_name(self.block_placed), block_placed: to_wasm_block_name(self.block_placed),
block_placed_against: to_wasm_block_name(self.block_placed_against), block_placed_against: to_wasm_block_name(self.block_placed_against),
block_position: to_wasm_block_position(self.block_position), block_pos: to_wasm_block_position(self.block_position),
can_build: self.can_build, can_build: self.can_build,
cancelled: self.cancelled, cancelled: self.cancelled,
}) })
@ -188,7 +188,7 @@ impl ToFromWasmEvent for BlockPlaceEvent {
player: consume_player(state, &data.player), player: consume_player(state, &data.player),
block_placed: from_wasm_block_name(&data.block_placed), block_placed: from_wasm_block_name(&data.block_placed),
block_placed_against: from_wasm_block_name(&data.block_placed_against), block_placed_against: from_wasm_block_name(&data.block_placed_against),
block_position: from_wasm_block_position(data.block_position), block_position: from_wasm_block_position(data.block_pos),
can_build: data.can_build, can_build: data.can_build,
cancelled: data.cancelled, cancelled: data.cancelled,
}, },

View File

@ -59,14 +59,18 @@ pub(super) const fn from_wasm_position(
pub(super) const fn to_wasm_block_position( pub(super) const fn to_wasm_block_position(
position: BlockPos, position: BlockPos,
) -> pumpkin::plugin::common::BlockPosition { ) -> pumpkin::plugin::common::BlockPos {
(position.0.x, position.0.y, position.0.z) pumpkin::plugin::common::BlockPos {
x: position.0.x,
y: position.0.y,
z: position.0.z,
}
} }
pub(super) const fn from_wasm_block_position( pub(super) const fn from_wasm_block_position(
position: pumpkin::plugin::common::BlockPosition, position: pumpkin::plugin::common::BlockPos,
) -> BlockPos { ) -> BlockPos {
BlockPos::new(position.0, position.1, position.2) BlockPos::new(position.x, position.y, position.z)
} }
pub(super) fn to_wasm_block_name(block: &'static Block) -> String { pub(super) fn to_wasm_block_name(block: &'static Block) -> String {

View File

@ -5,10 +5,11 @@ use crate::plugin::{
wit::v0_1::{ wit::v0_1::{
events::{ events::{
ToFromWasmEvent, consume_player, consume_text_component, consume_world, ToFromWasmEvent, consume_player, consume_text_component, consume_world,
from_wasm_click_type, from_wasm_entity_interaction_action, from_wasm_entity_type, from_wasm_block_name, from_wasm_block_position, from_wasm_click_type,
from_wasm_game_mode, from_wasm_hand, from_wasm_position, to_wasm_block_position, from_wasm_entity_interaction_action, from_wasm_entity_type, from_wasm_game_mode,
to_wasm_click_type, to_wasm_entity_interaction_action, to_wasm_entity_type, from_wasm_hand, from_wasm_position, to_wasm_block_position, to_wasm_click_type,
to_wasm_game_mode, to_wasm_hand, to_wasm_position, to_wasm_entity_interaction_action, to_wasm_entity_type, to_wasm_game_mode,
to_wasm_hand, to_wasm_position,
}, },
pumpkin::plugin::event::{ pumpkin::plugin::event::{
BedrockFormResponseEventData, CustomClickActionEventData, Event, BedrockFormResponseEventData, CustomClickActionEventData, Event,
@ -647,6 +648,15 @@ const fn to_wasm_interact_action(action: &InteractAction) -> WasmInteractAction
} }
} }
const fn from_wasm_interact_action(action: WasmInteractAction) -> InteractAction {
match action {
WasmInteractAction::LeftClickBlock => InteractAction::LeftClickBlock,
WasmInteractAction::LeftClickAir => InteractAction::LeftClickAir,
WasmInteractAction::RightClickAir => InteractAction::RightClickAir,
WasmInteractAction::RightClickBlock => InteractAction::RightClickBlock,
}
}
impl ToFromWasmEvent for PlayerInteractEvent { impl ToFromWasmEvent for PlayerInteractEvent {
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event { fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
let player = state let player = state
@ -662,11 +672,15 @@ impl ToFromWasmEvent for PlayerInteractEvent {
}) })
} }
fn from_wasm_event(event: Event, _state: &mut PluginHostState) -> Self { fn from_wasm_event(event: Event, state: &mut PluginHostState) -> Self {
match event { match event {
Event::PlayerInteractEvent(_data) => { Event::PlayerInteractEvent(data) => Self {
panic!("from_wasm_event for PlayerInteractEvent is not supported") player: consume_player(state, &data.player),
} action: from_wasm_interact_action(data.action),
clicked_pos: data.clicked_pos.map(from_wasm_block_position),
block: from_wasm_block_name(&data.block),
cancelled: data.cancelled,
},
_ => panic!("unexpected event type"), _ => panic!("unexpected event type"),
} }
} }

View File

@ -6,9 +6,14 @@ use pumpkin_world::world::BlockFlags;
use std::sync::Arc; use std::sync::Arc;
use wasmtime::component::Resource; use wasmtime::component::Resource;
use crate::block::entities::chest::ChestBlockEntity as InternalChestBlockEntity;
use crate::block::entities::command_block::CommandBlockEntity as InternalCommandBlockEntity;
use crate::block::entities::jukebox::JukeboxBlockEntity as InternalJukeboxBlockEntity;
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity as InternalMobSpawnerBlockEntity;
use crate::block::entities::sign::SignBlockEntity as InternalSignBlockEntity;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::{ use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::{
BlockDirection as WitBlockDirection, BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType, BlockFlags as WitBlockFlags,
BlockState as WitBlockState, PistonBehavior as WitPistonBehavior, BlockPos as WitBlockPos, BlockState as WitBlockState, PistonBehavior as WitPistonBehavior,
}; };
use crate::plugin::loader::wasm::wasm_host::{ use crate::plugin::loader::wasm::wasm_host::{
state::{PluginHostState, TextComponentResource, WorldResource}, state::{PluginHostState, TextComponentResource, WorldResource},
@ -486,6 +491,69 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
Ok(entities) Ok(entities)
} }
async fn get_block_entity(
&mut self,
world: Resource<World>,
pos: WitBlockPos,
) -> wasmtime::Result<Option<BlockEntityType>> {
let world_provider = self.get_world_res(&world)?.provider.clone();
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
let block_entity = world_provider.get_block_entity(&internal_pos);
if let Some(be) = block_entity {
if be
.as_any()
.downcast_ref::<InternalCommandBlockEntity>()
.is_some()
{
let res: Resource<BlockEntity> = self.add_block_entity(be)?;
Ok(Some(BlockEntityType::CommandBlockEntity(
Resource::new_own(res.rep()),
)))
} else if be
.as_any()
.downcast_ref::<InternalSignBlockEntity>()
.is_some()
{
let res: Resource<BlockEntity> = self.add_block_entity(be)?;
Ok(Some(BlockEntityType::SignBlockEntity(Resource::new_own(
res.rep(),
))))
} else if be
.as_any()
.downcast_ref::<InternalJukeboxBlockEntity>()
.is_some()
{
let res: Resource<BlockEntity> = self.add_block_entity(be)?;
Ok(Some(BlockEntityType::JukeboxBlockEntity(
Resource::new_own(res.rep()),
)))
} else if be
.as_any()
.downcast_ref::<InternalChestBlockEntity>()
.is_some()
{
let res: Resource<BlockEntity> = self.add_block_entity(be)?;
Ok(Some(BlockEntityType::ChestBlockEntity(Resource::new_own(
res.rep(),
))))
} else if be
.as_any()
.downcast_ref::<InternalMobSpawnerBlockEntity>()
.is_some()
{
let res: Resource<BlockEntity> = self.add_block_entity(be)?;
Ok(Some(BlockEntityType::MobSpawnerBlockEntity(
Resource::new_own(res.rep()),
)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
async fn drop(&mut self, rep: Resource<World>) -> wasmtime::Result<()> { async fn drop(&mut self, rep: Resource<World>) -> wasmtime::Result<()> {
self.resource_table self.resource_table
.delete::<WorldResource>(Resource::new_own(rep.rep())) .delete::<WorldResource>(Resource::new_own(rep.rep()))