From cfeb5d80fc69e0bb3eaac19c848e35bc2fc52e1c Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 16 May 2026 19:39:21 +0200 Subject: [PATCH] feat(plugin-api): expand item stack --- pumpkin-plugin-wit/v0.1/command.wit | 3 +- pumpkin-plugin-wit/v0.1/common.wit | 28 -- pumpkin-plugin-wit/v0.1/event.wit | 3 +- pumpkin-plugin-wit/v0.1/gui.wit | 3 +- pumpkin-plugin-wit/v0.1/item-stack.wit | 59 +++ pumpkin-plugin-wit/v0.1/java-dialogs.wit | 2 +- pumpkin-plugin-wit/v0.1/player.wit | 3 +- pumpkin-plugin-wit/v0.1/world.wit | 12 + .../src/plugin/loader/wasm/wasm_host/state.rs | 9 + .../wasm/wasm_host/wit/v0_1/events/player.rs | 16 +- .../loader/wasm/wasm_host/wit/v0_1/gui.rs | 30 +- .../wasm/wasm_host/wit/v0_1/item_stack.rs | 352 ++++++++++++++++++ .../loader/wasm/wasm_host/wit/v0_1/mod.rs | 1 + .../loader/wasm/wasm_host/wit/v0_1/player.rs | 216 +++-------- .../loader/wasm/wasm_host/wit/v0_1/world.rs | 49 +++ pumpkin/src/world/mod.rs | 14 + 16 files changed, 588 insertions(+), 212 deletions(-) create mode 100644 pumpkin-plugin-wit/v0.1/item-stack.wit create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/item_stack.rs diff --git a/pumpkin-plugin-wit/v0.1/command.wit b/pumpkin-plugin-wit/v0.1/command.wit index 993028c02..00b105484 100644 --- a/pumpkin-plugin-wit/v0.1/command.wit +++ b/pumpkin-plugin-wit/v0.1/command.wit @@ -2,7 +2,8 @@ interface command { use %world.{%world, entity}; use player.{player}; use block-entity.{command-block-entity}; - use common.{position, block-position, locale, item-stack, game-mode}; + use common.{position, block-position, locale, game-mode}; + use item-stack.{item-stack}; use server.{server, difficulty}; use text.{text-component}; diff --git a/pumpkin-plugin-wit/v0.1/common.wit b/pumpkin-plugin-wit/v0.1/common.wit index edf02fda1..4166030e9 100644 --- a/pumpkin-plugin-wit/v0.1/common.wit +++ b/pumpkin-plugin-wit/v0.1/common.wit @@ -59,34 +59,6 @@ interface common { b: u8, } - use data-components.{data-component}; - use enchantments.{enchantment}; - - /// Represents an enchantment and its level. - record enchantment-value { - enchantment: enchantment, - level: u32, - } - - /// Represents a data component and its serialized value. - record data-component-value { - component: data-component, - /// The serialized value of the component. - value: list, - } - - /// Represents a stack of items with its type and count. - record item-stack { - /// The unique registry key (e.g., "minecraft:diamond"). - registry-key: string, - /// The number of items in the stack. - count: u8, - /// The data components associated with this item stack. - components: list, - /// The enchantments associated with this item stack. - enchantments: list, - } - /// Represents the type of click interaction in a GUI. enum click-type { left, diff --git a/pumpkin-plugin-wit/v0.1/event.wit b/pumpkin-plugin-wit/v0.1/event.wit index e4451bc70..496ea920f 100644 --- a/pumpkin-plugin-wit/v0.1/event.wit +++ b/pumpkin-plugin-wit/v0.1/event.wit @@ -1,5 +1,6 @@ interface event { - use common.{block-position, game-mode, hand, item-stack, position, click-type}; + use common.{block-position, game-mode, hand, position, click-type}; + use item-stack.{item-stack}; use player.{player}; use text.{text-component}; use %world.{%world}; diff --git a/pumpkin-plugin-wit/v0.1/gui.wit b/pumpkin-plugin-wit/v0.1/gui.wit index 58eb021eb..7e3cc4d32 100644 --- a/pumpkin-plugin-wit/v0.1/gui.wit +++ b/pumpkin-plugin-wit/v0.1/gui.wit @@ -1,6 +1,7 @@ interface gui { use text.{text-component}; - use common.{item-stack, click-type}; + use common.{click-type}; + use item-stack.{item-stack}; enum gui-type { generic-9x1, diff --git a/pumpkin-plugin-wit/v0.1/item-stack.wit b/pumpkin-plugin-wit/v0.1/item-stack.wit new file mode 100644 index 000000000..eccadff9b --- /dev/null +++ b/pumpkin-plugin-wit/v0.1/item-stack.wit @@ -0,0 +1,59 @@ +interface item-stack { + use data-components.{data-component}; + use enchantments.{enchantment}; + use text.{text-component}; + + /// Represents an enchantment and its level. + record enchantment-value { + enchantment: enchantment, + level: u32, + } + + /// Represents a data component and its serialized value. + record data-component-value { + component: data-component, + /// The serialized value of the component. + value: list, + } + + resource item-stack { + constructor(registry-key: string, count: u8); + + /// Returns the unique registry key of the item (e.g., "minecraft:diamond"). + get-registry-key: func() -> string; + + /// Returns the number of items in the stack. + get-count: func() -> u8; + /// Sets the number of items in the stack. + set-count: func(count: u8); + + /// Returns the maximum number of items that can be in this stack. + get-max-count: func() -> u8; + + /// Returns all enchantments on this item stack. + get-enchantments: func() -> list; + /// Adds an enchantment to this item stack. + add-enchantment: func(enchantment: enchantment, level: u32); + /// Removes an enchantment from this item stack. + remove-enchantment: func(enchantment: enchantment); + + /// Returns the lore of this item stack. + get-lore: func() -> list; + /// Sets the lore of this item stack. + set-lore: func(lore: list); + /// Adds a line to the lore of this item stack. + add-lore: func(line: text-component); + + /// Returns the custom name of this item stack, if set. + get-custom-name: func() -> option; + /// Sets the custom name of this item stack. + set-custom-name: func(name: option); + + /// Returns all data components on this item stack. + get-components: func() -> list; + /// Sets a data component on this item stack. + set-component: func(component: data-component, value: list); + /// Removes a data component from this item stack. + remove-component: func(component: data-component); + } +} diff --git a/pumpkin-plugin-wit/v0.1/java-dialogs.wit b/pumpkin-plugin-wit/v0.1/java-dialogs.wit index 711b56233..2c93e759f 100644 --- a/pumpkin-plugin-wit/v0.1/java-dialogs.wit +++ b/pumpkin-plugin-wit/v0.1/java-dialogs.wit @@ -3,7 +3,7 @@ /// and interactive inputs (checkboxes, text fields, sliders). interface java-dialogs { use text.{text-component}; - use common.{item-stack}; + use item-stack.{item-stack}; /// Represents the layout and button configuration of a dialog. enum dialog-type { diff --git a/pumpkin-plugin-wit/v0.1/player.wit b/pumpkin-plugin-wit/v0.1/player.wit index a28cd70c4..67a34e3e6 100644 --- a/pumpkin-plugin-wit/v0.1/player.wit +++ b/pumpkin-plugin-wit/v0.1/player.wit @@ -1,6 +1,7 @@ /// Represents a player connected to the Minecraft server. interface player { - use common.{hand, item-stack, position, game-mode}; + use common.{hand, position, game-mode}; + use item-stack.{item-stack}; use %world.{%world, entity}; use permission.{permission-level}; use text.{text-component}; diff --git a/pumpkin-plugin-wit/v0.1/world.wit b/pumpkin-plugin-wit/v0.1/world.wit index 1dc1f9587..256904df6 100644 --- a/pumpkin-plugin-wit/v0.1/world.wit +++ b/pumpkin-plugin-wit/v0.1/world.wit @@ -246,6 +246,18 @@ interface %world { /// Returns the minimum Y coordinate (bottom of the world). get-min-y: func() -> s32; + /// Returns the sky light level at the specified position. + get-sky-light: func(pos: block-pos) -> u8; + + /// Sets the sky light level at the specified position. + set-sky-light: func(pos: block-pos, level: u8); + + /// Returns the block light level at the specified position. + get-block-light: func(pos: block-pos) -> u8; + + /// Sets the block light level at the specified position. + set-block-light: func(pos: block-pos, level: u8); + /// Returns a list of all entities in this world. get-entities: func() -> list; } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs index 9d4773981..8fa597792 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -53,6 +53,7 @@ pub type CommandResource = WasmResource; pub type CommandSenderResource = WasmResource; pub type ConsumedArgsResource = WasmResource; pub type CommandNodeResource = WasmResource; +pub type ItemStackResource = WasmResource>>; pub type OwnedConsumedArgs = HashMap; @@ -220,6 +221,14 @@ impl PluginHostState { let resource = self.resource_table.push(CommandNodeResource { provider })?; Ok(wasmtime::component::Resource::new_own(resource.rep())) } + + pub fn add_item_stack( + &mut self, + provider: Arc>, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(ItemStackResource { 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/events/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs index 90dd9015f..1cce10aee 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 @@ -10,7 +10,6 @@ use crate::plugin::{ to_wasm_click_type, to_wasm_entity_interaction_action, to_wasm_entity_type, to_wasm_game_mode, to_wasm_hand, to_wasm_position, }, - player::to_wit_item_stack, pumpkin::plugin::event::{ BedrockFormResponseEventData, CustomClickActionEventData, Event, InteractAction as WasmInteractAction, InventoryClickEventData, @@ -105,6 +104,9 @@ impl ToFromWasmEvent for InventoryCloseEvent { } } +use std::sync::Arc; +use tokio::sync::Mutex; + impl ToFromWasmEvent for InventoryClickEvent { fn to_wasm_event(&self, state: &mut PluginHostState) -> Event { let player = state @@ -117,8 +119,16 @@ impl ToFromWasmEvent for InventoryClickEvent { click_type: to_wasm_click_type(self.click_type), slot: self.slot, raw_slot: self.raw_slot, - clicked_item: self.clicked_item.as_ref().and_then(to_wit_item_stack), - cursor: self.cursor.as_ref().and_then(to_wit_item_stack), + clicked_item: self.clicked_item.as_ref().map(|stack| { + state + .add_item_stack(Arc::new(Mutex::new(stack.clone()))) + .expect("failed to add item stack resource") + }), + cursor: self.cursor.as_ref().map(|stack| { + state + .add_item_stack(Arc::new(Mutex::new(stack.clone()))) + .expect("failed to add item stack resource") + }), hotbar_button: self.hotbar_button, cancelled: self.cancelled, }) diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs index e58bf6f87..054c3eeee 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs @@ -2,13 +2,12 @@ use std::sync::Arc; use tokio::sync::Mutex; use wasmtime::component::Resource; -use super::player::{from_wit_item_stack, to_wit_item_stack}; use crate::plugin::api::gui::{PluginGui, PluginInventory}; use crate::plugin::loader::wasm::wasm_host::{ state::{GuiResource, PluginHostState}, wit::v0_1::pumpkin::plugin::{ - common::ItemStack as WitItemStack, gui::{self, Gui, GuiType}, + item_stack::ItemStack as WitHostItemStack, }, }; @@ -85,11 +84,12 @@ impl gui::HostGui for PluginHostState { &mut self, res: Resource, slot: u32, - item: WitItemStack, + item: Resource, ) -> wasmtime::Result<()> { let gui = self.get_gui_res(&res)?.provider.lock().await; if (slot as usize) < gui.inventory.slots.len() { - let item_stack = from_wit_item_stack(item); + let item_stack = self.get_item_stack(&item)?; + let item_stack = item_stack.lock().await.clone(); *gui.inventory.slots[slot as usize].lock().await = item_stack; } Ok(()) @@ -99,11 +99,23 @@ impl gui::HostGui for PluginHostState { &mut self, res: Resource, slot: u32, - ) -> wasmtime::Result> { - let gui = self.get_gui_res(&res)?.provider.lock().await; - if (slot as usize) < gui.inventory.slots.len() { - let stack = gui.inventory.slots[slot as usize].lock().await; - Ok(to_wit_item_stack(&stack)) + ) -> wasmtime::Result>> { + let stack = { + let gui = self.get_gui_res(&res)?.provider.lock().await; + if (slot as usize) < gui.inventory.slots.len() { + let stack = gui.inventory.slots[slot as usize].lock().await; + if stack.is_empty() { + None + } else { + Some(stack.clone()) + } + } else { + None + } + }; + + if let Some(stack) = stack { + Ok(Some(self.add_item_stack(Arc::new(Mutex::new(stack)))?)) } else { Ok(None) } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/item_stack.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/item_stack.rs new file mode 100644 index 000000000..41e2cc29f --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/item_stack.rs @@ -0,0 +1,352 @@ +use crate::plugin::loader::wasm::wasm_host::state::{ItemStackResource, PluginHostState}; +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::data_components::DataComponent as WitDataComponent; +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::enchantments::Enchantment as WitEnchantment; +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::item_stack::{ + DataComponentValue as WitDataComponentValue, EnchantmentValue as WitEnchantmentValue, + Host as ItemStackInterfaceHost, HostItemStack, ItemStack as ItemStackHandle, +}; +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent as WitTextComponent; +use std::sync::Arc; +use tokio::sync::Mutex; +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::player::{ + WitSeqAccess, text_component_from_resource, +}; +use pumpkin_data::Enchantment; +use pumpkin_data::data_component::DataComponent; +use pumpkin_data::data_component_impl::{CustomNameImpl, EnchantmentsImpl}; +use pumpkin_protocol::codec::data_component::{deserialize, serialize}; +use pumpkin_protocol::ser::deserializer::Deserializer; +use pumpkin_protocol::ser::serializer::Serializer; +use serde::ser::SerializeStruct; +use serde::ser::Serializer as _; +use std::borrow::Cow; + +pub(crate) fn to_wit_data_component(id: DataComponent) -> WitDataComponent { + // Safety: WIT enum is generated in the same order as the internal enum + unsafe { std::mem::transmute(id as u8) } +} + +pub(crate) fn from_wit_data_component(id: WitDataComponent) -> DataComponent { + // Safety: WIT enum is generated in the same order as the internal enum + unsafe { std::mem::transmute(id as u8) } +} + +pub(crate) fn to_wit_enchantment(id: &Enchantment) -> WitEnchantment { + // Safety: WIT enum is generated in the same order as the internal enum + unsafe { std::mem::transmute(id.id) } +} + +pub(crate) fn from_wit_enchantment(id: WitEnchantment) -> &'static Enchantment { + // Safety: WIT enum is generated in the same order as the internal enum + Enchantment::from_id(id as u8).unwrap() +} + +impl PluginHostState { + pub fn get_item_stack( + &self, + res: &Resource, + ) -> wasmtime::Result>> { + self.resource_table + .get::(&Resource::new_own(res.rep())) + .map(|r| r.provider.clone()) + .map_err(wasmtime::Error::from) + } +} + +impl ItemStackInterfaceHost for PluginHostState {} + +impl HostItemStack for PluginHostState { + async fn new( + &mut self, + registry_key: String, + count: u8, + ) -> wasmtime::Result> { + let item = pumpkin_data::item::Item::from_registry_key( + registry_key + .strip_prefix("minecraft:") + .unwrap_or(®istry_key), + ) + .unwrap_or(&pumpkin_data::item::Item::AIR); + let stack = pumpkin_data::item_stack::ItemStack::new(count, item); + self.add_item_stack(Arc::new(Mutex::new(stack))) + } + + async fn get_registry_key( + &mut self, + res: Resource, + ) -> wasmtime::Result { + let stack = self.get_item_stack(&res)?; + let stack = stack.lock().await; + Ok(stack.item.registry_key.to_string()) + } + + async fn get_count(&mut self, res: Resource) -> wasmtime::Result { + let stack = self.get_item_stack(&res)?; + let stack = stack.lock().await; + Ok(stack.item_count) + } + + async fn set_count( + &mut self, + res: Resource, + count: u8, + ) -> wasmtime::Result<()> { + let stack = self.get_item_stack(&res)?; + let mut stack = stack.lock().await; + stack.item_count = count; + Ok(()) + } + + async fn get_max_count(&mut self, res: Resource) -> wasmtime::Result { + let stack = self.get_item_stack(&res)?; + let stack = stack.lock().await; + // Search in components for MaxStackSize + if let Some((_, data)) = stack + .item + .components + .iter() + .find(|(id, _)| *id == DataComponent::MaxStackSize) + && let Some(max_size) = data + .as_any() + .downcast_ref::() + { + return Ok(max_size.size); + } + Ok(64) // Default + } + + async fn get_enchantments( + &mut self, + res: Resource, + ) -> wasmtime::Result> { + let stack = self.get_item_stack(&res)?; + let stack = stack.lock().await; + let mut enchantments = Vec::new(); + if let Some((_, Some(data))) = stack + .patch + .iter() + .find(|(id, _)| *id == DataComponent::Enchantments) + && let Some(enc_impl) = data.as_any().downcast_ref::() + { + for (enc, level) in enc_impl.enchantment.iter() { + enchantments.push(WitEnchantmentValue { + enchantment: to_wit_enchantment(enc), + level: *level as u32, + }); + } + } + Ok(enchantments) + } + + async fn add_enchantment( + &mut self, + res: Resource, + enchantment: WitEnchantment, + level: u32, + ) -> wasmtime::Result<()> { + let stack = self.get_item_stack(&res)?; + let mut stack = stack.lock().await; + let enc = from_wit_enchantment(enchantment); + + let mut current_encs = if let Some((_, Some(data))) = stack + .patch + .iter() + .find(|(id, _)| *id == DataComponent::Enchantments) + { + data.as_any() + .downcast_ref::() + .map(|e| e.enchantment.clone().into_owned()) + .unwrap_or_default() + } else { + Vec::new() + }; + + current_encs.retain(|(e, _)| e.id != enc.id); + current_encs.push((enc, level as i32)); + + if let Some((_, data)) = stack + .patch + .iter_mut() + .find(|(id, _)| *id == DataComponent::Enchantments) + { + *data = Some(Box::new(EnchantmentsImpl { + enchantment: Cow::from(current_encs), + })); + } else { + stack.patch.push(( + DataComponent::Enchantments, + Some(Box::new(EnchantmentsImpl { + enchantment: Cow::from(current_encs), + })), + )); + } + Ok(()) + } + + async fn remove_enchantment( + &mut self, + res: Resource, + enchantment: WitEnchantment, + ) -> wasmtime::Result<()> { + let stack = self.get_item_stack(&res)?; + let mut stack = stack.lock().await; + let enc = from_wit_enchantment(enchantment); + + if let Some((_, Some(data))) = stack + .patch + .iter_mut() + .find(|(id, _)| *id == DataComponent::Enchantments) + && let Some(enc_impl) = data.as_mut_any().downcast_mut::() + { + let mut encs = enc_impl.enchantment.clone().into_owned(); + encs.retain(|(e, _)| e.id != enc.id); + enc_impl.enchantment = Cow::from(encs); + } + Ok(()) + } + + async fn get_lore( + &mut self, + res: Resource, + ) -> wasmtime::Result>> { + let _stack = self.get_item_stack(&res)?; + // LoreImpl is currently not fully implemented with data. + Ok(Vec::new()) + } + + async fn set_lore( + &mut self, + res: Resource, + _lore: Vec>, + ) -> wasmtime::Result<()> { + let _stack = self.get_item_stack(&res)?; + Ok(()) + } + + async fn add_lore( + &mut self, + res: Resource, + _line: Resource, + ) -> wasmtime::Result<()> { + let _stack = self.get_item_stack(&res)?; + Ok(()) + } + + async fn get_custom_name( + &mut self, + res: Resource, + ) -> wasmtime::Result>> { + let stack = self.get_item_stack(&res)?; + let stack = stack.lock().await; + if let Some((_, Some(data))) = stack + .patch + .iter() + .find(|(id, _)| *id == DataComponent::CustomName) + && let Some(name_impl) = data.as_any().downcast_ref::() + { + let text = pumpkin_util::text::TextComponent::text(name_impl.name.clone()); + return Ok(Some(self.add_text_component(text)?)); + } + Ok(None) + } + + async fn set_custom_name( + &mut self, + res: Resource, + name: Option>, + ) -> wasmtime::Result<()> { + let stack = self.get_item_stack(&res)?; + let mut stack = stack.lock().await; + if let Some(name_res) = name { + let text = text_component_from_resource(self, &name_res); + // This is a hack to get some string representation. + let name_str = format!("{:?}", text.0.content); + if let Some((_, data)) = stack + .patch + .iter_mut() + .find(|(id, _)| *id == DataComponent::CustomName) + { + *data = Some(Box::new(CustomNameImpl { name: name_str })); + } else { + stack.patch.push(( + DataComponent::CustomName, + Some(Box::new(CustomNameImpl { name: name_str })), + )); + } + } else { + stack + .patch + .retain(|(id, _)| *id != DataComponent::CustomName); + } + Ok(()) + } + + async fn get_components( + &mut self, + res: Resource, + ) -> wasmtime::Result> { + let stack = self.get_item_stack(&res)?; + let stack = stack.lock().await; + let mut components = Vec::new(); + for (id, data) in &stack.patch { + if let Some(data) = data { + let mut buf = Vec::new(); + let mut serializer = Serializer::new(&mut buf); + let mut struct_ser = serializer.serialize_struct("", 0).unwrap(); + serialize(*id, data.as_ref(), &mut struct_ser).unwrap(); + struct_ser.end().unwrap(); + + components.push(WitDataComponentValue { + component: to_wit_data_component(*id), + value: buf, + }); + } + } + Ok(components) + } + + async fn set_component( + &mut self, + res: Resource, + component: WitDataComponent, + value: Vec, + ) -> wasmtime::Result<()> { + let stack = self.get_item_stack(&res)?; + let mut stack = stack.lock().await; + let id = from_wit_data_component(component); + let mut deserializer = Deserializer::new(value.as_slice()); + let mut seq = WitSeqAccess { + deserializer: &mut deserializer, + }; + + if let Ok(component_impl) = deserialize(id, &mut seq) { + if let Some((_, data)) = stack.patch.iter_mut().find(|(pid, _)| *pid == id) { + *data = Some(component_impl); + } else { + stack.patch.push((id, Some(component_impl))); + } + } + Ok(()) + } + + async fn remove_component( + &mut self, + res: Resource, + component: WitDataComponent, + ) -> wasmtime::Result<()> { + let stack = self.get_item_stack(&res)?; + let mut stack = stack.lock().await; + let id = from_wit_data_component(component); + stack.patch.retain(|(pid, _)| *pid != id); + Ok(()) + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + self.resource_table + .delete::(Resource::new_own(rep.rep())) + .map_err(wasmtime::Error::from)?; + Ok(()) + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs index 3b8c44ff1..27a43c7b0 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs @@ -17,6 +17,7 @@ pub mod forms; pub mod generated_packets; pub mod gui; pub mod i18n; +pub mod item_stack; pub mod java_dialogs; pub mod logging; pub mod permission; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index 92dcc8c1e..623f6883d 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -47,8 +47,12 @@ use pumpkin_protocol::bedrock::client::set_actor_data::{ use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_util::version::{BedrockMinecraftVersion, JavaMinecraftVersion}; -struct WitSeqAccess<'a> { - deserializer: &'a mut Deserializer<&'a [u8]>, +use pumpkin_protocol::ser::ReadingError; +use pumpkin_protocol::ser::deserializer::Deserializer; +use serde::de::SeqAccess; + +pub(crate) struct WitSeqAccess<'a> { + pub(crate) deserializer: &'a mut Deserializer<&'a [u8]>, } impl<'de> SeqAccess<'de> for WitSeqAccess<'_> { @@ -494,132 +498,6 @@ fn world_from_resource( .clone() } -use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::DataComponentValue as WitDataComponentValue; -use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::common::EnchantmentValue as WitEnchantmentValue; -use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::data_components::DataComponent as WitDataComponent; -use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::enchantments::Enchantment as WitEnchantment; -use pumpkin_data::Enchantment; -use pumpkin_data::data_component::DataComponent; -use pumpkin_data::data_component_impl::{DataComponentImpl, EnchantmentsImpl}; -use pumpkin_protocol::codec::data_component::{deserialize, serialize}; -use pumpkin_protocol::ser::ReadingError; -use pumpkin_protocol::ser::deserializer::Deserializer; -use pumpkin_protocol::ser::serializer::Serializer; -use serde::de::SeqAccess; -use serde::ser::SerializeStruct; -use serde::ser::Serializer as _; -use std::borrow::Cow; - -pub(super) fn to_wit_data_component(id: DataComponent) -> WitDataComponent { - // Safety: WIT enum is generated in the same order as the internal enum - unsafe { std::mem::transmute(id as u8) } -} - -pub(super) fn from_wit_data_component(id: WitDataComponent) -> DataComponent { - // Safety: WIT enum is generated in the same order as the internal enum - unsafe { std::mem::transmute(id as u8) } -} - -pub(super) fn to_wit_enchantment(id: &Enchantment) -> WitEnchantment { - // Safety: WIT enum is generated in the same order as the internal enum - unsafe { std::mem::transmute(id.id) } -} - -pub(super) fn from_wit_enchantment(id: WitEnchantment) -> &'static Enchantment { - // Safety: WIT enum is generated in the same order as the internal enum - Enchantment::from_id(id as u8).unwrap() -} - -pub(super) fn to_wit_item_stack( - stack: &pumpkin_data::item_stack::ItemStack, -) -> Option { - if stack.is_empty() { - return None; - } - - let mut components = Vec::new(); - let mut enchantments = Vec::new(); - for (id, data) in &stack.patch { - if let Some(data) = data { - if *id == DataComponent::Enchantments { - if let Some(enc_impl) = data.as_any().downcast_ref::() { - for (enc, level) in enc_impl.enchantment.iter() { - enchantments.push(WitEnchantmentValue { - enchantment: to_wit_enchantment(enc), - level: *level as u32, - }); - } - } - continue; - } - - let mut buf = Vec::new(); - let mut serializer = Serializer::new(&mut buf); - let mut struct_ser = serializer.serialize_struct("", 0).unwrap(); - serialize(*id, data.as_ref(), &mut struct_ser).unwrap(); - struct_ser.end().unwrap(); - - components.push(WitDataComponentValue { - component: to_wit_data_component(*id), - value: buf, - }); - } - } - - Some(pumpkin::plugin::common::ItemStack { - registry_key: stack.item.registry_key.to_string(), - count: stack.item_count, - components, - enchantments, - }) -} - -pub(super) fn from_wit_item_stack( - stack: pumpkin::plugin::common::ItemStack, -) -> pumpkin_data::item_stack::ItemStack { - let mut patch: Vec<(DataComponent, Option>)> = Vec::new(); - for component_value in stack.components { - let id = from_wit_data_component(component_value.component); - let mut deserializer = Deserializer::new(component_value.value.as_slice()); - - let mut seq = WitSeqAccess { - deserializer: &mut deserializer, - }; - - if let Ok(component_impl) = deserialize(id, &mut seq) { - patch.push((id, Some(component_impl))); - } - } - - if !stack.enchantments.is_empty() { - let mut encs = Vec::new(); - for enc_val in stack.enchantments { - encs.push(( - from_wit_enchantment(enc_val.enchantment), - enc_val.level as i32, - )); - } - patch.push(( - DataComponent::Enchantments, - Some(Box::new(EnchantmentsImpl { - enchantment: Cow::from(encs), - })), - )); - } - - pumpkin_data::item_stack::ItemStack::new_with_component( - stack.count, - pumpkin_data::item::Item::from_registry_key( - stack - .registry_key - .strip_prefix("minecraft:") - .unwrap_or(&stack.registry_key), - ) - .unwrap_or(&pumpkin_data::item::Item::AIR), - patch, - ) -} - const fn to_wit_permission_level( level: PermissionLvl, ) -> pumpkin::plugin::permission::PermissionLevel { @@ -705,18 +583,21 @@ use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::java::client::play::CSetContainerSlot; use pumpkin_world::inventory::Inventory; +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::item_stack::ItemStack as WitHostItemStack; + impl pumpkin::plugin::player::HostPlayer for PluginHostState { async fn set_item_in_hand( &mut self, player: Resource, hand: pumpkin::plugin::common::Hand, - stack: Option, + stack: Option>, ) -> wasmtime::Result<()> { let player = player_from_resource(self, &player)?; - let stack = stack.map_or_else( - || pumpkin_data::item_stack::ItemStack::EMPTY.clone(), - from_wit_item_stack, - ); + let stack = if let Some(stack_res) = stack { + self.get_item_stack(&stack_res)?.lock().await.clone() + } else { + pumpkin_data::item_stack::ItemStack::EMPTY.clone() + }; let hand = from_wasm_hand(hand); let slot = match hand { @@ -738,13 +619,14 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { &mut self, player: Resource, slot: u8, - stack: Option, + stack: Option>, ) -> wasmtime::Result<()> { let player = player_from_resource(self, &player)?; - let stack = stack.map_or_else( - || pumpkin_data::item_stack::ItemStack::EMPTY.clone(), - from_wit_item_stack, - ); + let stack = if let Some(stack_res) = stack { + self.get_item_stack(&stack_res)?.lock().await.clone() + } else { + pumpkin_data::item_stack::ItemStack::EMPTY.clone() + }; player .inventory() @@ -759,6 +641,35 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(()) } + async fn get_inventory_item( + &mut self, + player: Resource, + slot: u8, + ) -> wasmtime::Result>> { + let player = player_from_resource(self, &player)?; + let stack = player.inventory().get_stack(slot as usize).await; + if stack.lock().await.is_empty() { + Ok(None) + } else { + Ok(Some(self.add_item_stack(stack)?)) + } + } + + async fn get_item_in_hand( + &mut self, + player: Resource, + hand: pumpkin::plugin::common::Hand, + ) -> wasmtime::Result>> { + let player = player_from_resource(self, &player)?; + let hand = from_wasm_hand(hand); + let stack = player.inventory().get_stack_in_hand(hand).await; + if stack.lock().await.is_empty() { + Ok(None) + } else { + Ok(Some(self.add_item_stack(stack)?)) + } + } + async fn as_entity( &mut self, player: Resource, @@ -1188,35 +1099,6 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(player.inventory.get_selected_slot()) } - async fn get_item_in_hand( - &mut self, - player: Resource, - hand: pumpkin::plugin::common::Hand, - ) -> wasmtime::Result> { - let player = player_from_resource(self, &player)?; - let inventory = player.inventory(); - let item_stack = match hand { - pumpkin::plugin::common::Hand::Left => inventory.off_hand_item().await, - pumpkin::plugin::common::Hand::Right => inventory.held_item(), - }; - let item_stack = item_stack.lock().await.clone(); - Ok(to_wit_item_stack(&item_stack)) - } - - async fn get_inventory_item( - &mut self, - player: Resource, - slot: u8, - ) -> wasmtime::Result> { - let player = player_from_resource(self, &player)?; - let slot = slot as usize; - if slot >= PlayerInventory::MAIN_SIZE { - return Ok(None); - } - let item_stack = player.inventory.main_inventory[slot].lock().await.clone(); - Ok(to_wit_item_stack(&item_stack)) - } - async fn get_health(&mut self, player: Resource) -> wasmtime::Result { let player = player_from_resource(self, &player)?; Ok(player.living_entity.health.load()) 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 ce18aa760..98e4e558d 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 @@ -354,6 +354,55 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { Ok(self.get_world_res(&world)?.provider.min_y) } + async fn get_sky_light( + &mut self, + world: Resource, + pos: WitBlockPos, + ) -> wasmtime::Result { + let world_ref = self.get_world_res(&world)?; + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + Ok(world_ref.provider.get_sky_light_level(&internal_pos)) + } + + async fn set_sky_light( + &mut self, + world: Resource, + pos: WitBlockPos, + level: u8, + ) -> wasmtime::Result<()> { + let world_ref = self.get_world_res(&world)?; + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + world_ref.provider.set_sky_light_level(&internal_pos, level); + Ok(()) + } + + async fn get_block_light( + &mut self, + world: Resource, + pos: WitBlockPos, + ) -> wasmtime::Result { + let world_ref = self.get_world_res(&world)?; + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + Ok(world_ref + .provider + .get_block_light_level(&internal_pos) + .unwrap_or(0)) + } + + async fn set_block_light( + &mut self, + world: Resource, + pos: WitBlockPos, + level: u8, + ) -> wasmtime::Result<()> { + let world_ref = self.get_world_res(&world)?; + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + world_ref + .provider + .set_block_light_level(&internal_pos, level); + Ok(()) + } + async fn get_entities( &mut self, world: Resource, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index e73812fcf..3ba45a57c 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -3428,6 +3428,20 @@ impl World { .get_sky_light_level(&self.level, position) } + pub fn set_block_light_level(&self, position: &BlockPos, light_level: u8) { + let _ = self + .level + .light_engine + .set_block_light_level(&self.level, position, light_level); + } + + pub fn set_sky_light_level(&self, position: &BlockPos, light_level: u8) { + let _ = self + .level + .light_engine + .set_sky_light_level(&self.level, position, light_level); + } + pub fn schedule_block_tick( &self, block: &Block,