diff --git a/crates/valence_protocol/src/item.rs b/crates/valence_protocol/src/item.rs index 1080abca7..3fac674a0 100644 --- a/crates/valence_protocol/src/item.rs +++ b/crates/valence_protocol/src/item.rs @@ -1,7 +1,8 @@ use std::io::Write; +use uuid::Uuid; pub use valence_generated::item::ItemKind; -use valence_nbt::Compound; +use valence_nbt::{compound, Compound, List}; use crate::{Decode, Encode}; @@ -43,6 +44,95 @@ impl ItemStack { self } + /// This function takes the "Value" of the skin you want to apply to a + /// PlayerHead. The "Value" is a Base64-encoded JSON object that is + /// usually provided by websites. To learn more: + /// + /// # Errors + /// This function returns an error if the [ItemStack] you call it on isn't a + /// PlayerHead + /// + /// # Examples + /// ``` + /// // Value provided by https://minecraft-heads.com/ + /// let value = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzlmOWRkOGQ5MjQ0NTg0NWIwODM1MmZjMmY0OTRjYTE4OGJmNWMzNzFmM2JmOWQwMWJiNzRkOGVlNTk3YmM1YSJ9fX0="; + /// let head = ItemStack::new(ItemKind::PlayerHead, 1, None).with_playerhead_texture_value(value).unwrap(); + /// ``` + /// + /// Simple head command. More examples of Valence's command system: + /// ``` + /// #[derive(Command)] + /// #[paths("head {username}")] + /// struct HeadCommand { + /// username: String, + /// } + /// + /// fn command_handler( + /// mut command_events: EventReader>, + /// mut clients_query: Query<(&mut Client, &Username, &Properties, &mut Inventory)>, + /// ) { + /// for event in command_events.read() { + /// let target_username = &event.result.username; + /// + /// let target = if !target_username.is_empty() { + /// clients_query + /// .iter() + /// .find(|(_, username, _, _)| username.0 == *target_username) + /// } else { + /// Some(clients_query.get(event.executor).unwrap()) + /// }; + /// + /// if let Some(target) = target { + /// let properties = target.2; + /// let textures = properties.textures().unwrap(); + /// + /// // Construct a PlayerHead using `with_playerhead_texture_value` + /// let head = ItemStack::new(ItemKind::PlayerHead, 1, None) + /// .with_playerhead_texture_value(textures.value.clone()) + /// .unwrap(); + /// + /// let (_, _, _, mut inventory) = clients_query.get_mut(event.executor).unwrap(); + /// inventory.set_slot(36, head); + /// } else { + /// let (mut client, _, _, _) = clients_query.get_mut(event.executor).unwrap(); + /// client.send_chat_message( + /// "No player with that username found on the server".color(Color::RED), + /// ); + /// } + /// } + /// } + /// ``` + #[must_use] + pub fn with_playerhead_texture_value( + mut self, + texture_value: impl Into, + ) -> Result { + if self.item != ItemKind::PlayerHead { + return Err(()); + } + + let new_nbt = compound! { + "SkullOwner" => compound! { + "Id" => Uuid::default(), + "Properties" => compound! { + "textures" => List::Compound(vec![ + compound! { + "Value" => texture_value.into() + } + ]) + } + } + }; + + if let Some(nbt) = &mut self.nbt { + nbt.merge(new_nbt); + } else { + self.nbt = Some(new_nbt); + } + + Ok(self) + } + pub const fn is_empty(&self) -> bool { matches!(self.item, ItemKind::Air) || self.count <= 0 }