diff --git a/pumpkin-codegen/src/wit/data_component.rs b/pumpkin-codegen/src/wit/data_component.rs new file mode 100644 index 000000000..b7ad3e59a --- /dev/null +++ b/pumpkin-codegen/src/wit/data_component.rs @@ -0,0 +1,38 @@ +use semver::Version; +use std::collections::BTreeMap; +use std::fs; +use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; + +pub fn build() -> String { + let data_components: BTreeMap = + serde_json::from_str(&fs::read_to_string("../assets/data_component.json").unwrap()) + .expect("Failed to parse data_component.json"); + + let mut package = Package::new(PackageName::new( + "pumpkin", + "plugin", + Some(Version::new(0, 1, 0)), + )); + let mut interface = Interface::new("data-components"); + + let mut component_enum = Enum::empty(); + let mut data_component_vec = data_components.iter().collect::>(); + data_component_vec.sort_by_key(|(_, i)| **i); + + for (raw_name, _) in data_component_vec { + let name = raw_name + .strip_prefix("minecraft:") + .unwrap_or(raw_name) + .replace('_', "-") + .replace('/', "-"); + component_enum.case(name); + } + + interface.type_def(TypeDef::new( + "data-component", + TypeDefKind::Enum(component_enum), + )); + package.interface(interface); + + package.to_string() +} diff --git a/pumpkin-codegen/src/wit/enchantment.rs b/pumpkin-codegen/src/wit/enchantment.rs new file mode 100644 index 000000000..bde4928b5 --- /dev/null +++ b/pumpkin-codegen/src/wit/enchantment.rs @@ -0,0 +1,37 @@ +use semver::Version; +use std::collections::BTreeMap; +use std::fs; +use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; + +pub fn build() -> String { + let enchantments: BTreeMap = + serde_json::from_str(&fs::read_to_string("../assets/enchantments.json").unwrap()) + .expect("Failed to parse enchantments.json"); + + let mut package = Package::new(PackageName::new( + "pumpkin", + "plugin", + Some(Version::new(0, 1, 0)), + )); + let mut interface = Interface::new("enchantments"); + + let mut enchantment_enum = Enum::empty(); + let mut enchantment_vec = enchantments.keys().collect::>(); + enchantment_vec.sort(); + + for raw_name in enchantment_vec { + let name = raw_name + .strip_prefix("minecraft:") + .unwrap_or(raw_name) + .replace('_', "-"); + enchantment_enum.case(name); + } + + interface.type_def(TypeDef::new( + "enchantment", + TypeDefKind::Enum(enchantment_enum), + )); + package.interface(interface); + + package.to_string() +} diff --git a/pumpkin-codegen/src/wit/mod.rs b/pumpkin-codegen/src/wit/mod.rs index c16f10da0..ed4b488ae 100644 --- a/pumpkin-codegen/src/wit/mod.rs +++ b/pumpkin-codegen/src/wit/mod.rs @@ -1,4 +1,6 @@ pub mod bedrock_packet; +pub mod data_component; +pub mod enchantment; pub mod entity_type; pub mod java_packet; pub mod packet_mapping; @@ -26,6 +28,8 @@ pub fn main() { (entity_type::build, "entity-types.wit"), (java_packet::build, "java-packets.wit"), (bedrock_packet::build, "bedrock-packets.wit"), + (data_component::build, "data-components.wit"), + (enchantment::build, "enchantments.wit"), ]; for (build_fn, file) in build_functions { diff --git a/pumpkin-codegen/src/wit/packet_mapping.rs b/pumpkin-codegen/src/wit/packet_mapping.rs index 8482e0545..767e34c7a 100644 --- a/pumpkin-codegen/src/wit/packet_mapping.rs +++ b/pumpkin-codegen/src/wit/packet_mapping.rs @@ -137,11 +137,6 @@ fn parse_packet_file( field_inits.push_str(&format!(" {}: data.{}.clone(),\n", field_name, wit_field)); } }, - "VarInt" => { - // WIT maps s32 to i32, so try_into is needed if WIT field is i32 and VarInt(i32) - // Actually data.wit_field is i32. - field_inits.push_str(&format!(" {}: VarInt(data.{}),\n", field_name, wit_field)); - }, "VarUInt" => { field_inits.push_str(&format!(" {}: pumpkin_protocol::codec::var_uint::VarUInt(data.{}.try_into().unwrap()),\n", field_name, wit_field)); }, @@ -174,8 +169,29 @@ fn parse_packet_file( } } }, - "i32" | "u32" | "i64" | "u64" | "bool" | "f32" | "f64" | "u8" | "i8" | "u16" | "i16" => { - field_inits.push_str(&format!(" {}: data.{}.try_into().unwrap(),\n", field_name, wit_field)); + "i32" | "u32" | "i64" | "u64" | "bool" | "f32" | "f64" | "u8" | "i8" | "u16" | "i16" | "VarInt" => { + if is_slice { + if type_ident == "u8" { + if is_ref { + field_inits.push_str(&format!(" {}: &data.{},\n", field_name, wit_field)); + } else { + field_inits.push_str(&format!(" {}: data.{}.clone(),\n", field_name, wit_field)); + } + } else if type_ident == "VarInt" { + prep_code.push_str(&format!(" let vec_{}: Vec = data.{}.iter().map(|v| VarInt(*v)).collect();\n", wit_field, wit_field)); + if is_ref { + field_inits.push_str(&format!(" {}: &vec_{},\n", field_name, wit_field)); + } else { + field_inits.push_str(&format!(" {}: vec_{},\n", field_name, wit_field)); + } + } else { + field_inits.push_str(&format!(" {}: data.{}.iter().map(|v| *v as _).collect(),\n", field_name, wit_field)); + } + } else if type_ident == "VarInt" { + field_inits.push_str(&format!(" {}: VarInt(data.{}),\n", field_name, wit_field)); + } else { + field_inits.push_str(&format!(" {}: data.{}.try_into().unwrap(),\n", field_name, wit_field)); + } }, _ => { possible = false; diff --git a/pumpkin-plugin-wit/v0.1/common.wit b/pumpkin-plugin-wit/v0.1/common.wit index 5c511132d..edf02fda1 100644 --- a/pumpkin-plugin-wit/v0.1/common.wit +++ b/pumpkin-plugin-wit/v0.1/common.wit @@ -59,12 +59,32 @@ 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. diff --git a/pumpkin-plugin-wit/v0.1/data-components.wit b/pumpkin-plugin-wit/v0.1/data-components.wit new file mode 100644 index 000000000..cae754a25 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1/data-components.wit @@ -0,0 +1,116 @@ +package pumpkin:plugin@0.1.0; + +interface data-components { + enum data-component { + custom-data, + max-stack-size, + max-damage, + damage, + unbreakable, + use-effects, + custom-name, + minimum-attack-charge, + damage-type, + item-name, + item-model, + lore, + rarity, + enchantments, + can-place-on, + can-break, + attribute-modifiers, + custom-model-data, + tooltip-display, + repair-cost, + creative-slot-lock, + enchantment-glint-override, + intangible-projectile, + food, + consumable, + use-remainder, + use-cooldown, + damage-resistant, + tool, + weapon, + attack-range, + enchantable, + equippable, + repairable, + glider, + tooltip-style, + death-protection, + blocks-attacks, + piercing-weapon, + kinetic-weapon, + swing-animation, + additional-trade-cost, + stored-enchantments, + dye, + dyed-color, + map-color, + map-id, + map-decorations, + map-post-processing, + charged-projectiles, + bundle-contents, + potion-contents, + potion-duration-scale, + suspicious-stew-effects, + writable-book-content, + written-book-content, + trim, + debug-stick-state, + entity-data, + bucket-entity-data, + block-entity-data, + instrument, + provides-trim-material, + ominous-bottle-amplifier, + jukebox-playable, + provides-banner-patterns, + recipes, + lodestone-tracker, + firework-explosion, + fireworks, + profile, + note-block-sound, + banner-patterns, + base-color, + pot-decorations, + container, + block-state, + bees, + lock, + container-loot, + break-sound, + villager-variant, + wolf-variant, + wolf-sound-variant, + wolf-collar, + fox-variant, + salmon-size, + parrot-variant, + tropical-fish-pattern, + tropical-fish-base-color, + tropical-fish-pattern-color, + mooshroom-variant, + rabbit-variant, + pig-variant, + pig-sound-variant, + cow-variant, + cow-sound-variant, + chicken-variant, + chicken-sound-variant, + zombie-nautilus-variant, + frog-variant, + horse-variant, + painting-variant, + llama-variant, + axolotl-variant, + cat-variant, + cat-sound-variant, + cat-collar, + sheep-color, + shulker-color, + } +} diff --git a/pumpkin-plugin-wit/v0.1/enchantments.wit b/pumpkin-plugin-wit/v0.1/enchantments.wit new file mode 100644 index 000000000..21ab4c924 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1/enchantments.wit @@ -0,0 +1,49 @@ +package pumpkin:plugin@0.1.0; + +interface enchantments { + enum enchantment { + aqua-affinity, + bane-of-arthropods, + binding-curse, + blast-protection, + breach, + channeling, + density, + depth-strider, + efficiency, + feather-falling, + fire-aspect, + fire-protection, + flame, + fortune, + frost-walker, + impaling, + infinity, + knockback, + looting, + loyalty, + luck-of-the-sea, + lunge, + lure, + mending, + multishot, + piercing, + power, + projectile-protection, + protection, + punch, + quick-charge, + respiration, + riptide, + sharpness, + silk-touch, + smite, + soul-speed, + sweeping-edge, + swift-sneak, + thorns, + unbreaking, + vanishing-curse, + wind-burst, + } +} diff --git a/pumpkin-plugin-wit/v0.1/player.wit b/pumpkin-plugin-wit/v0.1/player.wit index 434e59cd1..a28cd70c4 100644 --- a/pumpkin-plugin-wit/v0.1/player.wit +++ b/pumpkin-plugin-wit/v0.1/player.wit @@ -644,6 +644,11 @@ interface player { /// Returns the item in the specified inventory slot. get-inventory-item: func (slot: u8) -> option; + /// Sets the item in the specified hand. + set-item-in-hand: func (hand: hand, stack: option); + /// Sets the item in the specified inventory slot. + set-inventory-item: func (slot: u8, stack: option); + // Status /// Returns the player's current health. diff --git a/pumpkin/src/plugin/cache.rs b/pumpkin/src/plugin/cache.rs index f19566932..ca80014f0 100644 --- a/pumpkin/src/plugin/cache.rs +++ b/pumpkin/src/plugin/cache.rs @@ -17,11 +17,10 @@ pub struct PermissionCache { impl PermissionCache { pub async fn load(path: &Path) -> Self { - if let Ok(data) = fs::read_to_string(path).await { - serde_json::from_str(&data).unwrap_or_default() - } else { - Self::default() - } + fs::read_to_string(path).await.map_or_else( + |_| Self::default(), + |data| serde_json::from_str(&data).unwrap_or_default(), + ) } pub async fn save(&self, path: &Path) -> tokio::io::Result<()> { @@ -31,13 +30,14 @@ impl PermissionCache { } pub async fn calculate_hash(path: &Path) -> tokio::io::Result { + use std::fmt::Write; + let bytes = fs::read(path).await?; let mut hasher = Sha256::new(); hasher.update(&bytes); let result = hasher.finalize(); - Ok(result - .iter() - .map(|b| format!("{b:02x}")) - .collect::>() - .join("")) + Ok(result.iter().fold(String::new(), |mut output, b| { + let _ = write!(output, "{b:02x}"); + output + })) } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/generated_packets.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/generated_packets.rs index d300bf146..67860d902 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/generated_packets.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/generated_packets.rs @@ -127,14 +127,13 @@ pub fn serialize_java_packet( Some(buf.into()) } ClientboundPacket::CCustomPayload(data) => { - todo!(); - // let p = pumpkin_protocol::java::client::play::CCustomPayload { - // channel: &data.channel, - // data: data.data, - // }; - // let mut buf = Vec::new(); - // crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); - // Some(buf.into()) + let p = pumpkin_protocol::java::client::play::CCustomPayload { + channel: &data.channel, + data: &data.data, + }; + let mut buf = Vec::new(); + crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); + Some(buf.into()) } ClientboundPacket::CPlayDisconnect(data) => { let component_reason = pumpkin_util::text::TextComponent::text(data.reason.clone()); @@ -294,7 +293,7 @@ pub fn serialize_java_packet( max_speed: data.max_speed.try_into().unwrap(), particle_count: data.particle_count.try_into().unwrap(), particle_id: VarInt(data.particle_id), - data: data.data.as_slice(), + data: &data.data, }; let mut buf = Vec::new(); crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); @@ -370,13 +369,13 @@ pub fn serialize_java_packet( Some(buf.into()) } ClientboundPacket::CRemoveEntities(data) => { - todo!(); - // let p = pumpkin_protocol::java::client::play::CRemoveEntities { - // entity_ids: VarInt(data.entity_ids), - // }; - // let mut buf = Vec::new(); - // crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); - // Some(buf.into()) + let vec_entity_ids: Vec = data.entity_ids.iter().map(|v| VarInt(*v)).collect(); + let p = pumpkin_protocol::java::client::play::CRemoveEntities { + entity_ids: &vec_entity_ids, + }; + let mut buf = Vec::new(); + crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); + Some(buf.into()) } ClientboundPacket::CRemoveMobEffect(data) => { let p = pumpkin_protocol::java::client::play::CRemoveMobEffect { @@ -451,14 +450,14 @@ pub fn serialize_java_packet( Some(buf.into()) } ClientboundPacket::CSetPassengers(data) => { - todo!(); - // let p = pumpkin_protocol::java::client::play::CSetPassengers { - // entity_id: VarInt(data.entity_id), - // passengers: VarInt(data.passengers), - // }; - // let mut buf = Vec::new(); - // crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); - // Some(buf.into()) + let vec_passengers: Vec = data.passengers.iter().map(|v| VarInt(*v)).collect(); + let p = pumpkin_protocol::java::client::play::CSetPassengers { + entity_id: VarInt(data.entity_id), + passengers: &vec_passengers, + }; + let mut buf = Vec::new(); + crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); + Some(buf.into()) } ClientboundPacket::CTitleText(data) => { let component_title = pumpkin_util::text::TextComponent::text(data.title.clone()); 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 08b132dac..e58bf6f87 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,6 +2,7 @@ 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}, @@ -10,7 +11,6 @@ use crate::plugin::loader::wasm::wasm_host::{ gui::{self, Gui, GuiType}, }, }; -use pumpkin_data::item_stack::ItemStack; impl PluginHostState { fn get_gui_res(&self, res: &Resource) -> wasmtime::Result<&GuiResource> { @@ -89,10 +89,7 @@ impl gui::HostGui for PluginHostState { ) -> wasmtime::Result<()> { let gui = self.get_gui_res(&res)?.provider.lock().await; if (slot as usize) < gui.inventory.slots.len() { - let item_id = item.registry_key; - let item_data = pumpkin_data::item::Item::from_registry_key(&item_id) - .ok_or_else(|| wasmtime::Error::msg("Invalid item"))?; - let item_stack = ItemStack::new(item.count, item_data); + let item_stack = from_wit_item_stack(item); *gui.inventory.slots[slot as usize].lock().await = item_stack; } Ok(()) @@ -106,14 +103,7 @@ impl gui::HostGui for PluginHostState { 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() { - Ok(None) - } else { - Ok(Some(WitItemStack { - registry_key: stack.get_item().registry_key.to_string(), - count: stack.item_count, - })) - } + Ok(to_wit_item_stack(&stack)) } else { Ok(None) } 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 1abb1904a..3b8c44ff1 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 @@ -37,6 +37,8 @@ bindgen!({ impl pumpkin::plugin::java_packets::Host for PluginHostState {} impl pumpkin::plugin::bedrock_packets::Host for PluginHostState {} +impl pumpkin::plugin::data_components::Host for PluginHostState {} +impl pumpkin::plugin::enchantments::Host for PluginHostState {} pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> { Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?; 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 5c2ca1d9d..92dcc8c1e 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,6 +47,22 @@ 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]>, +} + +impl<'de> SeqAccess<'de> for WitSeqAccess<'_> { + type Error = ReadingError; + + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + let val = seed.deserialize(&mut *self.deserializer)?; + Ok(Some(val)) + } +} + const fn to_wasm_java_version( version: JavaMinecraftVersion, ) -> pumpkin::plugin::player::JavaMinecraftVersion { @@ -478,19 +494,132 @@ 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.item_count == 0 { + 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 { @@ -571,7 +700,65 @@ impl pumpkin::plugin::player::Host for PluginHostState { Ok(players) } } +use crate::plugin::loader::wasm::wasm_host::wit::v0_1::events::from_wasm_hand; +use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; +use pumpkin_protocol::java::client::play::CSetContainerSlot; +use pumpkin_world::inventory::Inventory; + impl pumpkin::plugin::player::HostPlayer for PluginHostState { + async fn set_item_in_hand( + &mut self, + player: Resource, + hand: pumpkin::plugin::common::Hand, + 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 hand = from_wasm_hand(hand); + let slot = match hand { + pumpkin_util::Hand::Right => player.inventory().get_selected_slot() as usize, + pumpkin_util::Hand::Left => PlayerInventory::OFF_HAND_SLOT, + }; + + player.inventory().set_stack(slot, stack.clone()).await; + + // Sync to client + let stack_serializer = ItemStackSerializer::from(stack); + let packet = CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer); + player.client.enqueue_packet(&packet).await; + + Ok(()) + } + + async fn set_inventory_item( + &mut self, + player: Resource, + slot: u8, + 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, + ); + + player + .inventory() + .set_stack(slot as usize, stack.clone()) + .await; + + // Sync to client + let stack_serializer = ItemStackSerializer::from(stack); + let packet = CSetContainerSlot::new(0, 0, slot as i16, &stack_serializer); + player.client.enqueue_packet(&packet).await; + + Ok(()) + } + async fn as_entity( &mut self, player: Resource, diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index d38ecf168..f9a5e9f7a 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -750,39 +750,9 @@ impl PluginManager { for name in sorted_names { if let Some((instance, metadata, loader_data, loader, path)) = plugins_map.remove(&name) { - let hash = cache::calculate_hash(&path).await.unwrap_or_default(); - let allowed; - let mut wait_time = std::time::Duration::ZERO; - - if let Some(entry) = cache.entries.get(&hash) { - if entry.permissions_requested == metadata.permissions { - allowed = entry.approved; - info!( - "Found cached permission decision for plugin \"{}\" (approved: {})", - metadata.name, allowed - ); - } else { - (allowed, wait_time) = Self::ask_permission_confirmation(&metadata); - cache.entries.insert( - hash, - cache::PermissionCacheEntry { - permissions_requested: metadata.permissions.clone(), - approved: allowed, - }, - ); - let _ = cache.save(&cache_path).await; - } - } else { - (allowed, wait_time) = Self::ask_permission_confirmation(&metadata); - cache.entries.insert( - hash, - cache::PermissionCacheEntry { - permissions_requested: metadata.permissions.clone(), - approved: allowed, - }, - ); - let _ = cache.save(&cache_path).await; - } + let (allowed, wait_time) = self + .check_permissions_cached(&path, &metadata, &mut cache, &cache_path) + .await; total_wait_time += wait_time; @@ -812,6 +782,37 @@ impl PluginManager { Ok(total_wait_time) } + async fn check_permissions_cached( + &self, + path: &Path, + metadata: &PluginMetadata, + cache: &mut cache::PermissionCache, + cache_path: &Path, + ) -> (bool, std::time::Duration) { + let hash = cache::calculate_hash(path).await.unwrap_or_default(); + + if let Some(entry) = cache.entries.get(&hash) + && entry.permissions_requested == metadata.permissions + { + info!( + "Found cached permission decision for plugin \"{}\" (approved: {})", + metadata.name, entry.approved + ); + return (entry.approved, std::time::Duration::ZERO); + } + + let (allowed, wait_time) = Self::ask_permission_confirmation(metadata); + cache.entries.insert( + hash, + cache::PermissionCacheEntry { + permissions_requested: metadata.permissions.clone(), + approved: allowed, + }, + ); + let _ = cache.save(cache_path).await; + (allowed, wait_time) + } + /// Start loading a plugin asynchronously async fn start_loading_plugin( &self, @@ -823,39 +824,10 @@ impl PluginManager { let cache_path = Path::new(PLUGIN_DIR).join("permission_cache.json"); let mut cache = cache::PermissionCache::load(&cache_path).await; - let hash = cache::calculate_hash(path).await.unwrap_or_default(); - let allowed; - - if let Some(entry) = cache.entries.get(&hash) { - if entry.permissions_requested == metadata.permissions { - allowed = entry.approved; - info!( - "Found cached permission decision for plugin \"{}\" (approved: {})", - metadata.name, allowed - ); - } else { - (allowed, _) = Self::ask_permission_confirmation(&metadata); - cache.entries.insert( - hash, - cache::PermissionCacheEntry { - permissions_requested: metadata.permissions.clone(), - approved: allowed, - }, - ); - let _ = cache.save(&cache_path).await; - } - } else { - (allowed, _) = Self::ask_permission_confirmation(&metadata); - cache.entries.insert( - hash, - cache::PermissionCacheEntry { - permissions_requested: metadata.permissions.clone(), - approved: allowed, - }, - ); - let _ = cache.save(&cache_path).await; - } + let (allowed, _) = self + .check_permissions_cached(path, &metadata, &mut cache, &cache_path) + .await; if !allowed { warn!(