mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(plugin-api): block resolution + block entity NBT host functions (#2403)
* feat(plugin-api): add resolve-block-state and set-block-entity-nbt * feat(plugin-api): implement get-block-entity-nbt Serializes block entity at position to raw NBT bytes via write_internal(). Returns None if no block entity exists. * feat(plugin-api): implement block-state-to-info --------- Co-authored-by: Jakub Palacky <kubik.palacky@gmail.com>
This commit is contained in:
Submodule crates/pumpkin-plugin-wit updated: 50962bf639...97c0281c8e
@@ -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<Option<u16>> {
|
||||
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<Option<WitBlockStateInfo>> {
|
||||
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<World>,
|
||||
pos: WitBlockPos,
|
||||
) -> wasmtime::Result<Option<Vec<u8>>> {
|
||||
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<World>,
|
||||
pos: WitBlockPos,
|
||||
nbt_data: Vec<u8>,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
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<World>) -> wasmtime::Result<()> {
|
||||
self.resource_table
|
||||
.delete::<WorldResource>(Resource::new_own(rep.rep()))
|
||||
|
||||
Reference in New Issue
Block a user