Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility function to ItemStack for creating custom PlayerHeads #621

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
92 changes: 91 additions & 1 deletion crates/valence_protocol/src/item.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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: <https://minecraft.wiki/w/Item_format#Player_Heads>
///
/// # 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: <https://github.com/valence-rs/valence/blob/main/examples/command.rs>
/// ```
/// #[derive(Command)]
/// #[paths("head {username}")]
/// struct HeadCommand {
/// username: String,
/// }
///
/// fn command_handler(
/// mut command_events: EventReader<CommandResultEvent<HeadCommand>>,
/// 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<String>,
) -> Result<Self, ()> {
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
}
Expand Down
Loading