diff --git a/Cargo.lock b/Cargo.lock index 2497dd281..d87d41a28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2988,7 +2988,6 @@ dependencies = [ "pumpkin-util", "pumpkin-world", "serde", - "take_mut", "thiserror 2.0.18", "tokio", "uuid", @@ -3760,12 +3759,6 @@ dependencies = [ "winx", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "target-lexicon" version = "0.13.5" diff --git a/Cargo.toml b/Cargo.toml index fe6dc6a6d..5fe08bc86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -169,7 +169,6 @@ tracing-subscriber = { version = "0.3.23", features = [ "time", ] } slotmap = "1.1" -take_mut = "0.2.2" temp-dir = "0.2.0" thread_local = "1.1.9" time = "0.3" diff --git a/pumpkin-plugin-wit/v0.1/block-entity.wit b/pumpkin-plugin-wit/v0.1/block-entity.wit index c13c047b7..4a380871a 100644 --- a/pumpkin-plugin-wit/v0.1/block-entity.wit +++ b/pumpkin-plugin-wit/v0.1/block-entity.wit @@ -1,22 +1,12 @@ interface block-entity { - use common.{block-position}; + use common.{block-pos}; resource block-entity { resource-location: func() -> string; - get-position: func() -> block-position; + get-position: func() -> block-pos; 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 option; - // set-block-state: func(block-state: block-state-id); - // on-block-replaced: func(world: simple-world, position: block-position); is-dirty: func() -> bool; clear-dirty: func(); - // to-property-delegate: func() -> option; - // to-experience-container: func() -> option; } resource command-block-entity { @@ -30,7 +20,65 @@ interface block-entity { 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, + 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 { 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), } } diff --git a/pumpkin-plugin-wit/v0.1/command.wit b/pumpkin-plugin-wit/v0.1/command.wit index 00b105484..7f071c2c6 100644 --- a/pumpkin-plugin-wit/v0.1/command.wit +++ b/pumpkin-plugin-wit/v0.1/command.wit @@ -2,7 +2,7 @@ interface command { use %world.{%world, entity}; use player.{player}; 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 server.{server, difficulty}; use text.{text-component}; @@ -145,7 +145,7 @@ interface command { block-predicate(string), time(s32), num(result), - block-pos(tuple), + block-pos(block-pos), pos3d(tuple), pos2d(tuple), rotation(tuple), diff --git a/pumpkin-plugin-wit/v0.1/common.wit b/pumpkin-plugin-wit/v0.1/common.wit index 4166030e9..282a219c1 100644 --- a/pumpkin-plugin-wit/v0.1/common.wit +++ b/pumpkin-plugin-wit/v0.1/common.wit @@ -5,7 +5,11 @@ interface common { type raw-text-component = list; /// A position in the world grid (integers). - type block-position = tuple; + record block-pos { + x: s32, + y: s32, + z: s32, + } /// A precise coordinate in the world (floats). type position = tuple; diff --git a/pumpkin-plugin-wit/v0.1/event.wit b/pumpkin-plugin-wit/v0.1/event.wit index f7e85adcc..841d3109e 100644 --- a/pumpkin-plugin-wit/v0.1/event.wit +++ b/pumpkin-plugin-wit/v0.1/event.wit @@ -1,5 +1,5 @@ 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 player.{player}; use text.{text-component}; @@ -163,7 +163,7 @@ interface event { record player-interact-event-data { player: player, action: interact-action, - clicked-pos: option, + clicked-pos: option, block: string, cancelled: bool } @@ -213,7 +213,7 @@ interface event { record block-redstone-event-data { target-world: %world, state-id: u16, - block-position: block-position, + block-pos: block-pos, old-current: s32, new-current: s32, cancelled: bool @@ -222,7 +222,7 @@ interface event { record block-break-event-data { player: option, block: string, - block-position: block-position, + block-pos: block-pos, exp: u32, should-drop: bool, cancelled: bool @@ -248,7 +248,7 @@ interface event { old-state-id: u16, new-block: string, new-state-id: u16, - block-position: block-position, + block-pos: block-pos, cancelled: bool } @@ -256,7 +256,7 @@ interface event { player: player, block-placed: string, block-placed-against: string, - block-position: block-position, + block-pos: block-pos, can-build: bool, cancelled: bool } @@ -280,10 +280,10 @@ interface event { record spawn-change-event-data { target-world: %world, - previous-position: block-position, + previous-position: block-pos, previous-yaw: f32, previous-pitch: f32, - new-position: block-position, + new-position: block-pos, new-yaw: f32, new-pitch: f32 } diff --git a/pumpkin-plugin-wit/v0.1/world.wit b/pumpkin-plugin-wit/v0.1/world.wit index c9e52d0cd..cccf57a82 100644 --- a/pumpkin-plugin-wit/v0.1/world.wit +++ b/pumpkin-plugin-wit/v0.1/world.wit @@ -1,5 +1,6 @@ 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 scoreboard.{scoreboard}; use particles.{particle}; @@ -99,13 +100,6 @@ interface %world { id: u16, } - /// A position in the world grid. - record block-pos { - x: s32, - y: s32, - z: s32, - } - resource entity { get-id: func() -> u32; get-uuid: func() -> uuid; @@ -118,29 +112,6 @@ interface %world { get-pitch: 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); set-velocity: func(velocity: position); get-velocity: func() -> position; @@ -158,6 +129,73 @@ interface %world { get-fire-ticks: func() -> 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; + + get-vehicle: func() -> option; + get-passengers: func() -> list; + 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(); /// Performs a raycast from the entity's eye position in its looking direction. @@ -172,6 +210,12 @@ interface %world { face: block-direction, } + /// Represents an axis-aligned bounding box. + record bounding-box { + min: position, + max: position, + } + /// Represents a cardinal direction or block face. enum block-direction { down, @@ -267,5 +311,8 @@ interface %world { /// Returns a list of all entities in this world. get-entities: func() -> list; + + /// Returns the block entity at the specified position, if any. + get-block-entity: func(pos: block-pos) -> option; } } diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 8b06c7022..18aaf40aa 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -33,7 +33,6 @@ hybrid-array = "0.4" async-compression = { workspace = true, features = ["tokio", "zlib", "deflate"] } flate2.workspace = true -take_mut.workspace = true bitflags.workspace = true [dev-dependencies] diff --git a/pumpkin-protocol/src/bedrock/packet_decoder.rs b/pumpkin-protocol/src/bedrock/packet_decoder.rs index 4bd830875..c217f6967 100644 --- a/pumpkin-protocol/src/bedrock/packet_decoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_decoder.rs @@ -82,11 +82,6 @@ impl UDPNetworkDecoder { &mut self, _key: &[u8; 16], ) -> 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(()) } diff --git a/pumpkin-protocol/src/bedrock/packet_encoder.rs b/pumpkin-protocol/src/bedrock/packet_encoder.rs index 965722fcd..bae3af02c 100644 --- a/pumpkin-protocol/src/bedrock/packet_encoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_encoder.rs @@ -107,11 +107,6 @@ impl UDPNetworkEncoder { &mut self, _key: &[u8; 16], ) -> 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(()) } diff --git a/pumpkin-protocol/src/java/packet_decoder.rs b/pumpkin-protocol/src/java/packet_decoder.rs index 0d76c1c5b..cc4325147 100644 --- a/pumpkin-protocol/src/java/packet_decoder.rs +++ b/pumpkin-protocol/src/java/packet_decoder.rs @@ -74,7 +74,7 @@ impl AsyncRead for DecryptionReader { /// Supports `ZLib` decoding/decompression /// Supports Aes128 Encryption pub struct TCPNetworkDecoder { - reader: DecryptionReader, + reader: Option>, compression: Option, payload_scratch: BytesMut, } @@ -82,7 +82,7 @@ pub struct TCPNetworkDecoder { impl TCPNetworkDecoder { pub fn new(reader: R) -> Self { Self { - reader: DecryptionReader::None(reader), + reader: Some(DecryptionReader::None(reader)), compression: None, payload_scratch: BytesMut::new(), } @@ -94,19 +94,27 @@ impl TCPNetworkDecoder { /// 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> { - if matches!(self.reader, DecryptionReader::Decrypt(_)) { + if matches!(self.reader, Some(DecryptionReader::Decrypt(_))) { return Err(PacketDecodeError::Message( "Encryption already enabled".into(), )); } let cipher = Aes128Cfb8Dec::new_from_slices(key, key) .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(()) } pub async fn get_raw_packet(&mut self) -> Result { - 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 .map_err(|err| match err { ReadingError::CleanEOF(_) => PacketDecodeError::ConnectionClosed, @@ -119,7 +127,7 @@ impl TCPNetworkDecoder { 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_uncompressed_packet_data_len = None; diff --git a/pumpkin-protocol/src/java/packet_encoder.rs b/pumpkin-protocol/src/java/packet_encoder.rs index 5c9a0dec0..d92f187d1 100644 --- a/pumpkin-protocol/src/java/packet_encoder.rs +++ b/pumpkin-protocol/src/java/packet_encoder.rs @@ -81,7 +81,7 @@ impl AsyncWrite for EncryptionWriter { /// Supports `ZLib` endecoding/compression /// Supports Aes128 Encryption pub struct TCPNetworkEncoder { - writer: EncryptionWriter, + writer: Option>, // compression and compression threshold compression: Option<(CompressionThreshold, CompressionLevel)>, // Reused compressor to avoid constructing zlib state per packet. @@ -93,7 +93,7 @@ pub struct TCPNetworkEncoder { impl TCPNetworkEncoder { pub const fn new(writer: W) -> Self { Self { - writer: EncryptionWriter::None(writer), + writer: Some(EncryptionWriter::None(writer)), compression: None, compressor: None, compression_scratch: Vec::new(), @@ -109,14 +109,17 @@ impl TCPNetworkEncoder { /// 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> { - if matches!(self.writer, EncryptionWriter::Encrypt(_)) { + if matches!(self.writer, Some(EncryptionWriter::Encrypt(_))) { return Err(PacketEncodeError::Message( "Encryption already enabled".into(), )); } let cipher = Aes128Cfb8Enc::new_from_slices(key, key) .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(()) } @@ -242,14 +245,16 @@ impl TCPNetworkEncoder { } full_packet_len_var_int - .encode_async(&mut self.writer) + .encode_async(self.writer.as_mut().unwrap()) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; data_len_var_int - .encode_async(&mut self.writer) + .encode_async(self.writer.as_mut().unwrap()) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; self.writer + .as_mut() + .unwrap() .write_all(&self.compression_scratch) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; @@ -274,14 +279,16 @@ impl TCPNetworkEncoder { } full_packet_len_var_int - .encode_async(&mut self.writer) + .encode_async(self.writer.as_mut().unwrap()) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; data_len_var_int - .encode_async(&mut self.writer) + .encode_async(self.writer.as_mut().unwrap()) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; self.writer + .as_mut() + .unwrap() .write_all(&packet_data) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; @@ -299,10 +306,12 @@ impl TCPNetworkEncoder { } full_packet_len_var_int - .encode_async(&mut self.writer) + .encode_async(self.writer.as_mut().unwrap()) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; self.writer + .as_mut() + .unwrap() .write_all(&packet_data) .await .map_err(|err| PacketEncodeError::Message(err.to_string()))?; @@ -313,6 +322,8 @@ impl TCPNetworkEncoder { pub async fn flush(&mut self) -> Result<(), PacketEncodeError> { self.writer + .as_mut() + .unwrap() .flush() .await .map_err(|err| PacketEncodeError::Message(err.to_string())) diff --git a/pumpkin/src/block/entities/sign.rs b/pumpkin/src/block/entities/sign.rs index d5fac373c..a1aedf49b 100644 --- a/pumpkin/src/block/entities/sign.rs +++ b/pumpkin/src/block/entities/sign.rs @@ -11,7 +11,7 @@ use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; use pumpkin_util::math::position::BlockPos; use tokio::sync::Mutex; -#[derive(Clone, Default)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] #[repr(i8)] pub enum DyeColor { White = 0, diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index cdc77601e..d9b9715d8 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -1936,6 +1936,7 @@ impl JavaClient { .await; } + #[allow(clippy::too_many_lines)] pub async fn handle_use_item_on( &self, player: &Arc, @@ -1985,6 +1986,26 @@ impl JavaClient { let world = entity.world.load_full(); 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); // Code based on the java class ServerPlayerInteractionManager diff --git a/pumpkin/src/plugin/api/events/player/player_interact_event.rs b/pumpkin/src/plugin/api/events/player/player_interact_event.rs index db3da2bcd..31e3af4be 100644 --- a/pumpkin/src/plugin/api/events/player/player_interact_event.rs +++ b/pumpkin/src/plugin/api/events/player/player_interact_event.rs @@ -60,10 +60,10 @@ impl PlayerInteractEvent { /// Enum representing possible player interaction actions. #[derive(Clone, PartialEq, Eq)] pub enum InteractAction { - /// Left-clicking the air + /// Left-clicking a block LeftClickBlock, - /// Left-clicking a block + /// Left-clicking the air LeftClickAir, /// Right-clicking the air diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs index 2cefd5e76..20ed97e5f 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -55,6 +55,7 @@ pub type ConsumedArgsResource = WasmResource; pub type CommandNodeResource = WasmResource; pub type ItemStackResource = WasmResource>>; pub type RecipeManagerResource = WasmResource>; +pub type BlockEntityResource = WasmResource>; pub type OwnedConsumedArgs = HashMap; @@ -240,6 +241,14 @@ impl PluginHostState { .push(RecipeManagerResource { provider })?; Ok(wasmtime::component::Resource::new_own(resource.rep())) } + + pub fn add_block_entity( + &mut self, + provider: Arc, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(BlockEntityResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } } pub struct PluginHttpHooks { diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/block_entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/block_entity.rs index 033ea39f6..0f4736d53 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/block_entity.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/block_entity.rs @@ -1,90 +1,539 @@ +use std::sync::Arc; 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::{ - state::PluginHostState, + state::{BlockEntityResource, PluginHostState}, wit::v0_1::pumpkin::{ self, plugin::{ - block_entity::{BlockEntity, CommandBlockEntity}, - common::BlockPosition, + block_entity::{ + 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::HostBlockEntity for PluginHostState { - async fn resource_location(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement resource_location lookup") - } +fn block_entity_from_resource( + state: &PluginHostState, + entity: &Resource, +) -> wasmtime::Result> { + state + .resource_table + .get::(&Resource::new_own(entity.rep())) + .map_err(|_| wasmtime::Error::msg("invalid block entity resource handle")) + .map(|resource| resource.provider.clone()) +} - async fn get_position( - &mut self, - _res: Resource, - ) -> wasmtime::Result { - todo!("Implement get_position lookup") - } - - async fn get_id(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement get_id lookup") - } - - async fn is_dirty(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement is_dirty lookup") - } - - async fn clear_dirty(&mut self, _res: Resource) -> wasmtime::Result<()> { - todo!("Implement clear_dirty logic") - } - - async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { - todo!(); +const fn from_wasm_dye_color(color: DyeColor) -> InternalDyeColor { + match color { + DyeColor::White => InternalDyeColor::White, + DyeColor::Orange => InternalDyeColor::Orange, + DyeColor::Magenta => InternalDyeColor::Magenta, + DyeColor::LightBlue => InternalDyeColor::LightBlue, + DyeColor::Yellow => InternalDyeColor::Yellow, + DyeColor::Lime => InternalDyeColor::Lime, + DyeColor::Pink => InternalDyeColor::Pink, + DyeColor::Gray => InternalDyeColor::Gray, + DyeColor::LightGray => InternalDyeColor::LightGray, + DyeColor::Cyan => InternalDyeColor::Cyan, + DyeColor::Purple => InternalDyeColor::Purple, + DyeColor::Blue => InternalDyeColor::Blue, + DyeColor::Brown => InternalDyeColor::Brown, + DyeColor::Green => InternalDyeColor::Green, + DyeColor::Red => InternalDyeColor::Red, + DyeColor::Black => InternalDyeColor::Black, } } -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) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &res)?; + Ok(entity.resource_location().to_string()) + } + + async fn get_position(&mut self, res: Resource) -> wasmtime::Result { + 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) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &res)?; + Ok(entity.get_id()) + } + + async fn is_dirty(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &res)?; + Ok(entity.is_dirty()) + } + + async fn clear_dirty(&mut self, res: Resource) -> wasmtime::Result<()> { + let entity = block_entity_from_resource(self, &res)?; + entity.clear_dirty(); + Ok(()) + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } +} + +impl HostCommandBlockEntity for PluginHostState { async fn get_block_entity( &mut self, - _res: Resource, + res: Resource, ) -> wasmtime::Result> { - todo!("Implement upcasting/mapping to BlockEntity") + let entity = self + .resource_table + .get::(&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( - &mut self, - _res: Resource, - ) -> wasmtime::Result { - todo!("Implement last_output lookup") + async fn last_output(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + if let Some(cmd) = entity.as_any().downcast_ref::() { + Ok(cmd.last_output.lock().await.clone()) + } else { + Err(wasmtime::Error::msg("Not a command block entity")) + } } - async fn track_output(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement track_output lookup") + async fn track_output(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result { - todo!("Implement success_count lookup") + async fn success_count(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result { - todo!("Implement command lookup") + async fn command(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + if let Some(cmd) = entity.as_any().downcast_ref::() { + Ok(cmd.command.lock().await.clone()) + } else { + Err(wasmtime::Error::msg("Not a command block entity")) + } } - async fn auto(&mut self, _res: Resource) -> wasmtime::Result { - todo!("Implement auto lookup") + async fn auto(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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( - &mut self, - _res: Resource, - ) -> wasmtime::Result { - todo!("Implement condition_met lookup") + async fn condition_met(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result { - todo!("Implement powered lookup") + async fn powered(&mut self, res: Resource) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result<()> { - todo!(); + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } +} + +impl HostSignBlockEntity for PluginHostState { + async fn get_block_entity( + &mut self, + res: Resource, + ) -> wasmtime::Result> { + let entity = self + .resource_table + .get::(&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, + ) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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, + text: SignText, + ) -> wasmtime::Result<()> { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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, + ) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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, + text: SignText, + ) -> wasmtime::Result<()> { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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, + waxed: bool, + ) -> wasmtime::Result<()> { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } +} + +impl HostJukeboxBlockEntity for PluginHostState { + async fn get_block_entity( + &mut self, + res: Resource, + ) -> wasmtime::Result> { + let entity = self + .resource_table + .get::(&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) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result<()> { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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, + length_in_ticks: u64, + ) -> wasmtime::Result<()> { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } +} + +impl HostChestBlockEntity for PluginHostState { + async fn get_block_entity( + &mut self, + res: Resource, + ) -> wasmtime::Result> { + let entity = self + .resource_table + .get::(&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) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } +} + +impl HostMobSpawnerBlockEntity for PluginHostState { + async fn get_block_entity( + &mut self, + res: Resource, + ) -> wasmtime::Result> { + let entity = self + .resource_table + .get::(&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, + ) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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, + ) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result { + let entity = block_entity_from_resource(self, &Resource::new_own(res.rep()))?; + entity + .as_any() + .downcast_ref::() + .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) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) } } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/commands/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/commands/mod.rs index 5cd6f7baa..81e05d6f2 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/commands/mod.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/commands/mod.rs @@ -43,7 +43,7 @@ use crate::{ Arg, ArgumentType, Command, CommandNode, CommandSender, CommandSenderType, ConsumedArgs, PermissionLevel, StringType, }, - common::{Locale, Position}, + common::{BlockPos as WitBlockPos, Locale, Position}, player::Player, server::Server, 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::Pos2D(v) => Arg::Pos2d((v.x, v.y)), OwnedArg::Rotation(a, b, c, d) => Arg::Rotation((a, b, c, d)), diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs index b3f2d4d05..f7604b284 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs @@ -1,6 +1,8 @@ use std::sync::Arc; use wasmtime::component::Resource; +use pumpkin_util::math::vector3::Vector3; + use crate::plugin::loader::wasm::wasm_host::{ state::{EntityResource, PluginHostState}, wit::v0_1::events::to_wasm_position, @@ -11,7 +13,8 @@ use crate::plugin::loader::wasm::wasm_host::{ text::TextComponent, uuid::Uuid, 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, @@ -283,8 +286,9 @@ impl HostEntity for PluginHostState { let entity = entity_from_resource(self, &entity)?; Ok(entity .get_entity() - .has_visual_fire - .load(std::sync::atomic::Ordering::Relaxed)) + .fire_ticks + .load(std::sync::atomic::Ordering::Relaxed) + > 0) } async fn set_on_fire( @@ -410,6 +414,370 @@ impl HostEntity for PluginHostState { Ok(()) } + async fn get_health(&mut self, entity: Resource) -> wasmtime::Result { + 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, 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) -> wasmtime::Result { + 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, 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) -> wasmtime::Result { + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + 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, 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + 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) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(to_wasm_position(entity.get_eye_pos())) + } + + async fn get_nearby_entities( + &mut self, + entity: Resource, + x: f64, + y: f64, + z: f64, + ) -> wasmtime::Result>> { + 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, + ) -> wasmtime::Result>> { + 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, + ) -> wasmtime::Result>> { + 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, + passenger: Resource, + ) -> 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, + passenger: Resource, + ) -> 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) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + let ids: Vec = 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, + ) -> wasmtime::Result { + 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) -> wasmtime::Result { + 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) -> wasmtime::Result { + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().entity_dimension.load().width) + } + + async fn get_height(&mut self, entity: Resource) -> wasmtime::Result { + let entity = entity_from_resource(self, &entity)?; + Ok(entity.get_entity().entity_dimension.load().height) + } + + async fn set_rotation( + &mut self, + entity: Resource, + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + 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, + 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) -> wasmtime::Result { + Ok(crate::entity::breath::MAX_AIR) + } + + async fn send_system_message( + &mut self, + entity: Resource, + message: Resource, + ) -> wasmtime::Result<()> { + let entity = entity_from_resource(self, &entity)?; + if let Some(player) = entity.get_player() { + let text_res = self + .resource_table + .get::( + &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) -> wasmtime::Result<()> { let entity = entity_from_resource(self, &entity)?; entity.get_entity().remove().await; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/block.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/block.rs index 98ef6592d..3afe4d5c8 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/block.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/block.rs @@ -28,7 +28,7 @@ impl ToFromWasmEvent for BlockRedstoneEvent { Event::BlockRedstoneEvent(BlockRedstoneEventData { target_world, 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, new_current: self.new_current, cancelled: self.cancelled, @@ -40,7 +40,7 @@ impl ToFromWasmEvent for BlockRedstoneEvent { Event::BlockRedstoneEvent(data) => Self { world: consume_world(state, &data.target_world), 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, new_current: data.new_current, cancelled: data.cancelled, @@ -61,7 +61,7 @@ impl ToFromWasmEvent for BlockBreakEvent { Event::BlockBreakEvent(BlockBreakEventData { player, 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, should_drop: self.drop, cancelled: self.cancelled, @@ -73,7 +73,7 @@ impl ToFromWasmEvent for BlockBreakEvent { Event::BlockBreakEvent(data) => Self { player: data.player.map(|player| consume_player(state, &player)), 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, drop: data.should_drop, cancelled: data.cancelled, @@ -145,7 +145,7 @@ impl ToFromWasmEvent for BlockGrowEvent { old_state_id: self.old_state_id, new_block: to_wasm_block_name(self.new_block), 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, }) } @@ -158,7 +158,7 @@ impl ToFromWasmEvent for BlockGrowEvent { old_state_id: data.old_state_id, new_block: from_wasm_block_name(&data.new_block), 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, }, _ => panic!("unexpected event type"), @@ -176,7 +176,7 @@ impl ToFromWasmEvent for BlockPlaceEvent { player, block_placed: to_wasm_block_name(self.block_placed), 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, cancelled: self.cancelled, }) @@ -188,7 +188,7 @@ impl ToFromWasmEvent for BlockPlaceEvent { player: consume_player(state, &data.player), block_placed: from_wasm_block_name(&data.block_placed), 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, cancelled: data.cancelled, }, diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs index b3b2ab789..a53474b8e 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs @@ -59,14 +59,18 @@ pub(super) const fn from_wasm_position( pub(super) const fn to_wasm_block_position( position: BlockPos, -) -> pumpkin::plugin::common::BlockPosition { - (position.0.x, position.0.y, position.0.z) +) -> pumpkin::plugin::common::BlockPos { + pumpkin::plugin::common::BlockPos { + x: position.0.x, + y: position.0.y, + z: position.0.z, + } } pub(super) const fn from_wasm_block_position( - position: pumpkin::plugin::common::BlockPosition, + position: pumpkin::plugin::common::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 { diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs index 1cce10aee..959825ab8 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs @@ -5,10 +5,11 @@ use crate::plugin::{ wit::v0_1::{ events::{ ToFromWasmEvent, consume_player, consume_text_component, consume_world, - from_wasm_click_type, from_wasm_entity_interaction_action, from_wasm_entity_type, - from_wasm_game_mode, from_wasm_hand, from_wasm_position, to_wasm_block_position, - to_wasm_click_type, to_wasm_entity_interaction_action, to_wasm_entity_type, - to_wasm_game_mode, to_wasm_hand, to_wasm_position, + from_wasm_block_name, from_wasm_block_position, from_wasm_click_type, + from_wasm_entity_interaction_action, from_wasm_entity_type, from_wasm_game_mode, + from_wasm_hand, from_wasm_position, to_wasm_block_position, to_wasm_click_type, + to_wasm_entity_interaction_action, to_wasm_entity_type, to_wasm_game_mode, + to_wasm_hand, to_wasm_position, }, pumpkin::plugin::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 { fn to_wasm_event(&self, state: &mut PluginHostState) -> Event { 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 { - Event::PlayerInteractEvent(_data) => { - panic!("from_wasm_event for PlayerInteractEvent is not supported") - } + Event::PlayerInteractEvent(data) => Self { + 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"), } } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs index d059dd35e..222a90fbb 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs @@ -6,9 +6,14 @@ use pumpkin_world::world::BlockFlags; use std::sync::Arc; 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::{ - BlockDirection as WitBlockDirection, BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, - BlockState as WitBlockState, PistonBehavior as WitPistonBehavior, + BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType, BlockFlags as WitBlockFlags, + BlockPos as WitBlockPos, BlockState as WitBlockState, PistonBehavior as WitPistonBehavior, }; use crate::plugin::loader::wasm::wasm_host::{ state::{PluginHostState, TextComponentResource, WorldResource}, @@ -486,6 +491,69 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { Ok(entities) } + async fn get_block_entity( + &mut self, + world: Resource, + pos: WitBlockPos, + ) -> wasmtime::Result> { + 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::() + .is_some() + { + let res: Resource = self.add_block_entity(be)?; + Ok(Some(BlockEntityType::CommandBlockEntity( + Resource::new_own(res.rep()), + ))) + } else if be + .as_any() + .downcast_ref::() + .is_some() + { + let res: Resource = self.add_block_entity(be)?; + Ok(Some(BlockEntityType::SignBlockEntity(Resource::new_own( + res.rep(), + )))) + } else if be + .as_any() + .downcast_ref::() + .is_some() + { + let res: Resource = self.add_block_entity(be)?; + Ok(Some(BlockEntityType::JukeboxBlockEntity( + Resource::new_own(res.rep()), + ))) + } else if be + .as_any() + .downcast_ref::() + .is_some() + { + let res: Resource = self.add_block_entity(be)?; + Ok(Some(BlockEntityType::ChestBlockEntity(Resource::new_own( + res.rep(), + )))) + } else if be + .as_any() + .downcast_ref::() + .is_some() + { + let res: Resource = 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) -> wasmtime::Result<()> { self.resource_table .delete::(Resource::new_own(rep.rep()))