feat(plugin-api): expand item stack

This commit is contained in:
Alexander Medvedev 2026-05-16 19:39:21 +02:00
parent 0c56e47f43
commit cfeb5d80fc
16 changed files with 588 additions and 212 deletions

View File

@ -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};

View File

@ -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<u8>,
}
/// 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<data-component-value>,
/// The enchantments associated with this item stack.
enchantments: list<enchantment-value>,
}
/// Represents the type of click interaction in a GUI.
enum click-type {
left,

View File

@ -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};

View File

@ -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,

View File

@ -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<u8>,
}
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<enchantment-value>;
/// 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<text-component>;
/// Sets the lore of this item stack.
set-lore: func(lore: list<text-component>);
/// 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<text-component>;
/// Sets the custom name of this item stack.
set-custom-name: func(name: option<text-component>);
/// Returns all data components on this item stack.
get-components: func() -> list<data-component-value>;
/// Sets a data component on this item stack.
set-component: func(component: data-component, value: list<u8>);
/// Removes a data component from this item stack.
remove-component: func(component: data-component);
}
}

View File

@ -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 {

View File

@ -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};

View File

@ -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<entity>;
}

View File

@ -53,6 +53,7 @@ pub type CommandResource = WasmResource<CommandTree>;
pub type CommandSenderResource = WasmResource<CommandSender>;
pub type ConsumedArgsResource = WasmResource<OwnedConsumedArgs>;
pub type CommandNodeResource = WasmResource<NonLeafNodeBuilder>;
pub type ItemStackResource = WasmResource<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>>;
pub type OwnedConsumedArgs = HashMap<String, OwnedArg>;
@ -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<T>(
&mut self,
provider: Arc<Mutex<pumpkin_data::item_stack::ItemStack>>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(ItemStackResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
}
pub struct PluginHttpHooks {

View File

@ -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,
})

View File

@ -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<Gui>,
slot: u32,
item: WitItemStack,
item: Resource<WitHostItemStack>,
) -> 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<Gui>,
slot: u32,
) -> wasmtime::Result<Option<WitItemStack>> {
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
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;
Ok(to_wit_item_stack(&stack))
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)
}

View File

@ -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<ItemStackHandle>,
) -> wasmtime::Result<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>> {
self.resource_table
.get::<ItemStackResource>(&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<Resource<ItemStackHandle>> {
let item = pumpkin_data::item::Item::from_registry_key(
registry_key
.strip_prefix("minecraft:")
.unwrap_or(&registry_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<ItemStackHandle>,
) -> wasmtime::Result<String> {
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<ItemStackHandle>) -> wasmtime::Result<u8> {
let stack = self.get_item_stack(&res)?;
let stack = stack.lock().await;
Ok(stack.item_count)
}
async fn set_count(
&mut self,
res: Resource<ItemStackHandle>,
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<ItemStackHandle>) -> wasmtime::Result<u8> {
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::<pumpkin_data::data_component_impl::MaxStackSizeImpl>()
{
return Ok(max_size.size);
}
Ok(64) // Default
}
async fn get_enchantments(
&mut self,
res: Resource<ItemStackHandle>,
) -> wasmtime::Result<Vec<WitEnchantmentValue>> {
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::<EnchantmentsImpl>()
{
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<ItemStackHandle>,
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::<EnchantmentsImpl>()
.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<ItemStackHandle>,
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::<EnchantmentsImpl>()
{
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<ItemStackHandle>,
) -> wasmtime::Result<Vec<Resource<WitTextComponent>>> {
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<ItemStackHandle>,
_lore: Vec<Resource<WitTextComponent>>,
) -> wasmtime::Result<()> {
let _stack = self.get_item_stack(&res)?;
Ok(())
}
async fn add_lore(
&mut self,
res: Resource<ItemStackHandle>,
_line: Resource<WitTextComponent>,
) -> wasmtime::Result<()> {
let _stack = self.get_item_stack(&res)?;
Ok(())
}
async fn get_custom_name(
&mut self,
res: Resource<ItemStackHandle>,
) -> wasmtime::Result<Option<Resource<WitTextComponent>>> {
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::<CustomNameImpl>()
{
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<ItemStackHandle>,
name: Option<Resource<WitTextComponent>>,
) -> 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<ItemStackHandle>,
) -> wasmtime::Result<Vec<WitDataComponentValue>> {
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<ItemStackHandle>,
component: WitDataComponent,
value: Vec<u8>,
) -> 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<ItemStackHandle>,
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<ItemStackHandle>) -> wasmtime::Result<()> {
self.resource_table
.delete::<ItemStackResource>(Resource::new_own(rep.rep()))
.map_err(wasmtime::Error::from)?;
Ok(())
}
}

View File

@ -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;

View File

@ -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<pumpkin::plugin::common::ItemStack> {
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::<EnchantmentsImpl>() {
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<Box<dyn DataComponentImpl>>)> = 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<Player>,
hand: pumpkin::plugin::common::Hand,
stack: Option<pumpkin::plugin::common::ItemStack>,
stack: Option<Resource<WitHostItemStack>>,
) -> 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<Player>,
slot: u8,
stack: Option<pumpkin::plugin::common::ItemStack>,
stack: Option<Resource<WitHostItemStack>>,
) -> 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<Player>,
slot: u8,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
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<Player>,
hand: pumpkin::plugin::common::Hand,
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
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<Player>,
@ -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<Player>,
hand: pumpkin::plugin::common::Hand,
) -> wasmtime::Result<Option<pumpkin::plugin::common::ItemStack>> {
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<Player>,
slot: u8,
) -> wasmtime::Result<Option<pumpkin::plugin::common::ItemStack>> {
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<Player>) -> wasmtime::Result<f32> {
let player = player_from_resource(self, &player)?;
Ok(player.living_entity.health.load())

View File

@ -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<World>,
pos: WitBlockPos,
) -> wasmtime::Result<u8> {
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<World>,
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<World>,
pos: WitBlockPos,
) -> wasmtime::Result<u8> {
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<World>,
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<World>,

View File

@ -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,