feat(plugin-api): parse data_component and enchantment to .wit

This commit is contained in:
Alexander Medvedev 2026-05-16 18:43:41 +02:00
parent c786bb0dc2
commit 0c56e47f43
14 changed files with 555 additions and 120 deletions

View File

@ -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<String, u8> =
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::<Vec<_>>();
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()
}

View File

@ -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<String, serde_json::Value> =
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::<Vec<_>>();
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()
}

View File

@ -1,4 +1,6 @@
pub mod bedrock_packet; pub mod bedrock_packet;
pub mod data_component;
pub mod enchantment;
pub mod entity_type; pub mod entity_type;
pub mod java_packet; pub mod java_packet;
pub mod packet_mapping; pub mod packet_mapping;
@ -26,6 +28,8 @@ pub fn main() {
(entity_type::build, "entity-types.wit"), (entity_type::build, "entity-types.wit"),
(java_packet::build, "java-packets.wit"), (java_packet::build, "java-packets.wit"),
(bedrock_packet::build, "bedrock-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 { for (build_fn, file) in build_functions {

View File

@ -137,11 +137,6 @@ fn parse_packet_file(
field_inits.push_str(&format!(" {}: data.{}.clone(),\n", field_name, wit_field)); 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" => { "VarUInt" => {
field_inits.push_str(&format!(" {}: pumpkin_protocol::codec::var_uint::VarUInt(data.{}.try_into().unwrap()),\n", field_name, wit_field)); 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" => { "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<VarInt> = 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)); field_inits.push_str(&format!(" {}: data.{}.try_into().unwrap(),\n", field_name, wit_field));
}
}, },
_ => { _ => {
possible = false; possible = false;

View File

@ -59,12 +59,32 @@ interface common {
b: u8, 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. /// Represents a stack of items with its type and count.
record item-stack { record item-stack {
/// The unique registry key (e.g., "minecraft:diamond"). /// The unique registry key (e.g., "minecraft:diamond").
registry-key: string, registry-key: string,
/// The number of items in the stack. /// The number of items in the stack.
count: u8, 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. /// Represents the type of click interaction in a GUI.

View File

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

View File

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

View File

@ -644,6 +644,11 @@ interface player {
/// Returns the item in the specified inventory slot. /// Returns the item in the specified inventory slot.
get-inventory-item: func (slot: u8) -> option<item-stack>; get-inventory-item: func (slot: u8) -> option<item-stack>;
/// Sets the item in the specified hand.
set-item-in-hand: func (hand: hand, stack: option<item-stack>);
/// Sets the item in the specified inventory slot.
set-inventory-item: func (slot: u8, stack: option<item-stack>);
// Status // Status
/// Returns the player's current health. /// Returns the player's current health.

View File

@ -17,11 +17,10 @@ pub struct PermissionCache {
impl PermissionCache { impl PermissionCache {
pub async fn load(path: &Path) -> Self { pub async fn load(path: &Path) -> Self {
if let Ok(data) = fs::read_to_string(path).await { fs::read_to_string(path).await.map_or_else(
serde_json::from_str(&data).unwrap_or_default() |_| Self::default(),
} else { |data| serde_json::from_str(&data).unwrap_or_default(),
Self::default() )
}
} }
pub async fn save(&self, path: &Path) -> tokio::io::Result<()> { 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<String> { pub async fn calculate_hash(path: &Path) -> tokio::io::Result<String> {
use std::fmt::Write;
let bytes = fs::read(path).await?; let bytes = fs::read(path).await?;
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(&bytes); hasher.update(&bytes);
let result = hasher.finalize(); let result = hasher.finalize();
Ok(result Ok(result.iter().fold(String::new(), |mut output, b| {
.iter() let _ = write!(output, "{b:02x}");
.map(|b| format!("{b:02x}")) output
.collect::<Vec<String>>() }))
.join(""))
} }

View File

@ -127,14 +127,13 @@ pub fn serialize_java_packet(
Some(buf.into()) Some(buf.into())
} }
ClientboundPacket::CCustomPayload(data) => { ClientboundPacket::CCustomPayload(data) => {
todo!(); let p = pumpkin_protocol::java::client::play::CCustomPayload {
// let p = pumpkin_protocol::java::client::play::CCustomPayload { channel: &data.channel,
// channel: &data.channel, data: &data.data,
// data: data.data, };
// }; let mut buf = Vec::new();
// let mut buf = Vec::new(); crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
// crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); Some(buf.into())
// Some(buf.into())
} }
ClientboundPacket::CPlayDisconnect(data) => { ClientboundPacket::CPlayDisconnect(data) => {
let component_reason = pumpkin_util::text::TextComponent::text(data.reason.clone()); 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(), max_speed: data.max_speed.try_into().unwrap(),
particle_count: data.particle_count.try_into().unwrap(), particle_count: data.particle_count.try_into().unwrap(),
particle_id: VarInt(data.particle_id), particle_id: VarInt(data.particle_id),
data: data.data.as_slice(), data: &data.data,
}; };
let mut buf = Vec::new(); let mut buf = Vec::new();
crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); 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()) Some(buf.into())
} }
ClientboundPacket::CRemoveEntities(data) => { ClientboundPacket::CRemoveEntities(data) => {
todo!(); let vec_entity_ids: Vec<VarInt> = data.entity_ids.iter().map(|v| VarInt(*v)).collect();
// let p = pumpkin_protocol::java::client::play::CRemoveEntities { let p = pumpkin_protocol::java::client::play::CRemoveEntities {
// entity_ids: VarInt(data.entity_ids), entity_ids: &vec_entity_ids,
// }; };
// let mut buf = Vec::new(); let mut buf = Vec::new();
// crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
// Some(buf.into()) Some(buf.into())
} }
ClientboundPacket::CRemoveMobEffect(data) => { ClientboundPacket::CRemoveMobEffect(data) => {
let p = pumpkin_protocol::java::client::play::CRemoveMobEffect { let p = pumpkin_protocol::java::client::play::CRemoveMobEffect {
@ -451,14 +450,14 @@ pub fn serialize_java_packet(
Some(buf.into()) Some(buf.into())
} }
ClientboundPacket::CSetPassengers(data) => { ClientboundPacket::CSetPassengers(data) => {
todo!(); let vec_passengers: Vec<VarInt> = data.passengers.iter().map(|v| VarInt(*v)).collect();
// let p = pumpkin_protocol::java::client::play::CSetPassengers { let p = pumpkin_protocol::java::client::play::CSetPassengers {
// entity_id: VarInt(data.entity_id), entity_id: VarInt(data.entity_id),
// passengers: VarInt(data.passengers), passengers: &vec_passengers,
// }; };
// let mut buf = Vec::new(); let mut buf = Vec::new();
// crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap(); crate::net::java::JavaClient::write_packet_for_version(&p, version, &mut buf).unwrap();
// Some(buf.into()) Some(buf.into())
} }
ClientboundPacket::CTitleText(data) => { ClientboundPacket::CTitleText(data) => {
let component_title = pumpkin_util::text::TextComponent::text(data.title.clone()); let component_title = pumpkin_util::text::TextComponent::text(data.title.clone());

View File

@ -2,6 +2,7 @@ use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use wasmtime::component::Resource; 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::api::gui::{PluginGui, PluginInventory};
use crate::plugin::loader::wasm::wasm_host::{ use crate::plugin::loader::wasm::wasm_host::{
state::{GuiResource, PluginHostState}, state::{GuiResource, PluginHostState},
@ -10,7 +11,6 @@ use crate::plugin::loader::wasm::wasm_host::{
gui::{self, Gui, GuiType}, gui::{self, Gui, GuiType},
}, },
}; };
use pumpkin_data::item_stack::ItemStack;
impl PluginHostState { impl PluginHostState {
fn get_gui_res(&self, res: &Resource<Gui>) -> wasmtime::Result<&GuiResource> { fn get_gui_res(&self, res: &Resource<Gui>) -> wasmtime::Result<&GuiResource> {
@ -89,10 +89,7 @@ impl gui::HostGui for PluginHostState {
) -> wasmtime::Result<()> { ) -> wasmtime::Result<()> {
let gui = self.get_gui_res(&res)?.provider.lock().await; let gui = self.get_gui_res(&res)?.provider.lock().await;
if (slot as usize) < gui.inventory.slots.len() { if (slot as usize) < gui.inventory.slots.len() {
let item_id = item.registry_key; let item_stack = from_wit_item_stack(item);
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);
*gui.inventory.slots[slot as usize].lock().await = item_stack; *gui.inventory.slots[slot as usize].lock().await = item_stack;
} }
Ok(()) Ok(())
@ -106,14 +103,7 @@ impl gui::HostGui for PluginHostState {
let gui = self.get_gui_res(&res)?.provider.lock().await; let gui = self.get_gui_res(&res)?.provider.lock().await;
if (slot as usize) < gui.inventory.slots.len() { if (slot as usize) < gui.inventory.slots.len() {
let stack = gui.inventory.slots[slot as usize].lock().await; let stack = gui.inventory.slots[slot as usize].lock().await;
if stack.is_empty() { Ok(to_wit_item_stack(&stack))
Ok(None)
} else {
Ok(Some(WitItemStack {
registry_key: stack.get_item().registry_key.to_string(),
count: stack.item_count,
}))
}
} else { } else {
Ok(None) Ok(None)
} }

View File

@ -37,6 +37,8 @@ bindgen!({
impl pumpkin::plugin::java_packets::Host for PluginHostState {} impl pumpkin::plugin::java_packets::Host for PluginHostState {}
impl pumpkin::plugin::bedrock_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<PluginHostState>) -> wasmtime::Result<()> { pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> {
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?; Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;

View File

@ -47,6 +47,22 @@ use pumpkin_protocol::bedrock::client::set_actor_data::{
use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_protocol::codec::var_ulong::VarULong;
use pumpkin_util::version::{BedrockMinecraftVersion, JavaMinecraftVersion}; 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<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: serde::de::DeserializeSeed<'de>,
{
let val = seed.deserialize(&mut *self.deserializer)?;
Ok(Some(val))
}
}
const fn to_wasm_java_version( const fn to_wasm_java_version(
version: JavaMinecraftVersion, version: JavaMinecraftVersion,
) -> pumpkin::plugin::player::JavaMinecraftVersion { ) -> pumpkin::plugin::player::JavaMinecraftVersion {
@ -478,19 +494,132 @@ fn world_from_resource(
.clone() .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( pub(super) fn to_wit_item_stack(
stack: &pumpkin_data::item_stack::ItemStack, stack: &pumpkin_data::item_stack::ItemStack,
) -> Option<pumpkin::plugin::common::ItemStack> { ) -> Option<pumpkin::plugin::common::ItemStack> {
if stack.item_count == 0 { if stack.is_empty() {
return None; 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 { Some(pumpkin::plugin::common::ItemStack {
registry_key: stack.item.registry_key.to_string(), registry_key: stack.item.registry_key.to_string(),
count: stack.item_count, 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( const fn to_wit_permission_level(
level: PermissionLvl, level: PermissionLvl,
) -> pumpkin::plugin::permission::PermissionLevel { ) -> pumpkin::plugin::permission::PermissionLevel {
@ -571,7 +700,65 @@ impl pumpkin::plugin::player::Host for PluginHostState {
Ok(players) 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 { 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>,
) -> 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<Player>,
slot: u8,
stack: Option<pumpkin::plugin::common::ItemStack>,
) -> 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( async fn as_entity(
&mut self, &mut self,
player: Resource<Player>, player: Resource<Player>,

View File

@ -750,39 +750,9 @@ impl PluginManager {
for name in sorted_names { for name in sorted_names {
if let Some((instance, metadata, loader_data, loader, path)) = plugins_map.remove(&name) 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, wait_time) = self
let allowed; .check_permissions_cached(&path, &metadata, &mut cache, &cache_path)
let mut wait_time = std::time::Duration::ZERO; .await;
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;
}
total_wait_time += wait_time; total_wait_time += wait_time;
@ -812,6 +782,37 @@ impl PluginManager {
Ok(total_wait_time) 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 /// Start loading a plugin asynchronously
async fn start_loading_plugin( async fn start_loading_plugin(
&self, &self,
@ -823,39 +824,10 @@ impl PluginManager {
let cache_path = Path::new(PLUGIN_DIR).join("permission_cache.json"); let cache_path = Path::new(PLUGIN_DIR).join("permission_cache.json");
let mut cache = cache::PermissionCache::load(&cache_path).await; let mut cache = cache::PermissionCache::load(&cache_path).await;
let hash = cache::calculate_hash(path).await.unwrap_or_default();
let allowed; let (allowed, _) = self
.check_permissions_cached(path, &metadata, &mut cache, &cache_path)
if let Some(entry) = cache.entries.get(&hash) { .await;
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;
}
if !allowed { if !allowed {
warn!( warn!(