diff --git a/crates/pumpkin-plugin-wit b/crates/pumpkin-plugin-wit index 50962bf63..97c0281c8 160000 --- a/crates/pumpkin-plugin-wit +++ b/crates/pumpkin-plugin-wit @@ -1 +1 @@ -Subproject commit 50962bf63943a48a5835a9f5db0eea4267c3035f +Subproject commit 97c0281c8e3c33741996f864c55d09e86f0d2728 diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs index 9421f8fe1..fb9cc0ecb 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs @@ -16,9 +16,10 @@ use crate::block::entities::sign::SignBlockEntity as InternalSignBlockEntity; use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::Position as WitPosition; use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::{ BlockDirection as WitBlockDirection, BlockEntity, BlockEntityType, BlockFlags as WitBlockFlags, - BlockPos as WitBlockPos, BlockState as WitBlockState, BoundingBox as WitBoundingBox, - Chunk as WitChunk, NoteblockInstrument as WitNoteblockInstrument, - PistonBehavior as WitPistonBehavior, WorldBorder as WitWorldBorder, + BlockPos as WitBlockPos, BlockState as WitBlockState, BlockStateInfo as WitBlockStateInfo, + BoundingBox as WitBoundingBox, Chunk as WitChunk, + NoteblockInstrument as WitNoteblockInstrument, PistonBehavior as WitPistonBehavior, + WorldBorder as WitWorldBorder, }; use crate::plugin::loader::wasm::wasm_host::{ state::{ @@ -193,7 +194,55 @@ impl PluginHostState { } } -impl pumpkin::plugin::world::Host for PluginHostState {} +impl pumpkin::plugin::world::Host for PluginHostState { + async fn resolve_block_state( + &mut self, + name: String, + properties: Vec<(String, String)>, + ) -> wasmtime::Result> { + let Some(block) = pumpkin_data::Block::from_name(&name) else { + return Ok(None); + }; + + if properties.is_empty() { + return Ok(Some(block.default_state.id.as_u16())); + } + + let props: Vec<(&str, &str)> = properties + .iter() + .map(|(k, v)| (k.as_str(), v.as_str())) + .collect(); + // from_properties/from_value panics on unknown property values, + // so catch panics to avoid crashing on schematics from other MC versions. + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let block_props = block.from_properties(&props); + block_props.to_state_id(block).as_u16() + })); + Ok(result.ok()) + } + + async fn block_state_to_info( + &mut self, + state_id: u16, + ) -> wasmtime::Result> { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let bsid = BlockStateId::new_or_air(state_id); + let block = pumpkin_data::Block::from_state_id(bsid); + let name = format!("minecraft:{}", block.name); + let properties = block + .properties(bsid) + .map(|p| { + p.to_props() + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect() + }) + .unwrap_or_default(); + WitBlockStateInfo { name, properties } + })); + Ok(result.ok()) + } +} impl pumpkin::plugin::particles::Host for PluginHostState {} impl pumpkin::plugin::sounds::Host for PluginHostState {} @@ -727,6 +776,54 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { block_entity.map_or_else(|| Ok(None), |be| self.get_wit_block_entity(be)) } + async fn get_block_entity_nbt( + &mut self, + world: Resource, + pos: WitBlockPos, + ) -> wasmtime::Result>> { + let world_ref = self.get_world_res(&world)?.provider.clone(); + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + + let Some(entity) = world_ref.get_block_entity(&internal_pos) else { + return Ok(None); + }; + + let mut nbt = pumpkin_nbt::NbtCompound::new(); + entity.write_internal(&mut nbt).await; + + let bytes = pumpkin_nbt::Nbt::from(nbt).write_unnamed(); + Ok(Some(bytes.to_vec())) + } + + async fn set_block_entity_nbt( + &mut self, + world: Resource, + pos: WitBlockPos, + nbt_data: Vec, + ) -> wasmtime::Result> { + let world_ref = self.get_world_res(&world)?.provider.clone(); + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + + let mut cursor = std::io::Cursor::new(&nbt_data[..]); + let mut reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new( + pumpkin_nbt::deserializer::NbtStreamReader(&mut cursor), + ); + let mut nbt: pumpkin_nbt::NbtCompound = pumpkin_nbt::Nbt::read_unnamed(&mut reader) + .map_err(|e| wasmtime::Error::msg(format!("Invalid NBT: {e}")))? + .root_tag; + + // Override NBT position with the caller-provided position so tile + // entities land at the correct coordinates during schematic pastes. + nbt.put_int("x", pos.x); + nbt.put_int("y", pos.y); + nbt.put_int("z", pos.z); + + // Use add_block_entity_nbt for lazy loading — avoids broadcasting + // a packet per tile entity during bulk operations. + world_ref.add_block_entity_nbt(internal_pos, &nbt); + Ok(Ok(())) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { self.resource_table .delete::(Resource::new_own(rep.rep()))