mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix(protocol): encode and decode renamed item names (#2301)
This commit is contained in:
@@ -62,6 +62,7 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option<Box<dyn DataCompone
|
||||
PotionContents => Some(PotionContentsImpl::read_data(data)?.to_dyn()),
|
||||
Fireworks => Some(FireworksImpl::read_data(data)?.to_dyn()),
|
||||
FireworkExplosion => Some(FireworkExplosionImpl::read_data(data)?.to_dyn()),
|
||||
CustomName => Some(CustomNameImpl::read_data(data)?.to_dyn()),
|
||||
ItemModel => Some(ItemModelImpl::read_data(data)?.to_dyn()),
|
||||
Consumable => Some(ConsumableImpl::read_data(data)?.to_dyn()),
|
||||
Equippable => Some(EquippableImpl::read_data(data)?.to_dyn()),
|
||||
@@ -203,16 +204,22 @@ impl DataComponentImpl for UnbreakableImpl {
|
||||
}
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct CustomNameImpl {
|
||||
// TODO make TextComponent
|
||||
pub name: String,
|
||||
pub name: TextComponent,
|
||||
}
|
||||
impl CustomNameImpl {
|
||||
fn read_data(data: &NbtTag) -> Option<Self> {
|
||||
data.extract_string().map(|name| Self {
|
||||
name: TextComponent::text(name.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for CustomNameImpl {
|
||||
fn write_data(&self) -> NbtTag {
|
||||
NbtTag::String(self.name.clone().into())
|
||||
NbtTag::String(self.name.clone().get_text().into())
|
||||
}
|
||||
|
||||
fn get_hash(&self) -> i32 {
|
||||
get_str_hash(self.name.as_str()) as i32
|
||||
get_str_hash(self.name.clone().get_text().as_str()) as i32
|
||||
}
|
||||
|
||||
default_impl!(CustomName);
|
||||
|
||||
@@ -395,7 +395,12 @@ impl ItemStack {
|
||||
|
||||
pub fn set_custom_name(&mut self, name: String) {
|
||||
use crate::data_component_impl::CustomNameImpl;
|
||||
let component = Some(CustomNameImpl { name }.to_dyn());
|
||||
let component = Some(
|
||||
CustomNameImpl {
|
||||
name: pumpkin_util::text::TextComponent::text(name),
|
||||
}
|
||||
.to_dyn(),
|
||||
);
|
||||
if let Some(pos) = self
|
||||
.patch
|
||||
.iter()
|
||||
|
||||
@@ -13,9 +13,10 @@ use pumpkin_data::data_component_impl::{
|
||||
use pumpkin_data::effect::StatusEffect;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::sound::Sound;
|
||||
use pumpkin_nbt::{serializer::NbtWriteHelperJava, tag::NbtTag};
|
||||
use serde::de;
|
||||
use serde::de::SeqAccess;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::ser::{SerializeStruct, Serializer};
|
||||
|
||||
const MAX_STATUS_EFFECTS: usize = 128;
|
||||
|
||||
@@ -349,14 +350,28 @@ impl DataComponentCodec<Self> for ItemModelImpl {
|
||||
|
||||
impl DataComponentCodec<Self> for CustomNameImpl {
|
||||
fn serialize<T: SerializeStruct>(&self, seq: &mut T) -> Result<(), T::Error> {
|
||||
seq.serialize_field::<String>("", &self.name)
|
||||
seq.serialize_field("", &NetworkTextNbtString(self.name.clone().get_text()))
|
||||
}
|
||||
|
||||
fn deserialize<'a, A: SeqAccess<'a>>(seq: &mut A) -> Result<Self, A::Error> {
|
||||
let name = seq
|
||||
.next_element::<String>()?
|
||||
.ok_or(de::Error::custom("No CustomNameImpl name string!"))?;
|
||||
Ok(Self { name })
|
||||
Ok(Self {
|
||||
name: pumpkin_util::text::TextComponent::text(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct NetworkTextNbtString(String);
|
||||
|
||||
impl serde::Serialize for NetworkTextNbtString {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut bytes = Vec::new();
|
||||
NbtTag::String(self.0.clone().into_boxed_str())
|
||||
.serialize(&mut NbtWriteHelperJava::new(&mut bytes))
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
serializer.serialize_bytes(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
use crate::VarInt;
|
||||
use crate::codec::data_component::{deserialize, serialize};
|
||||
use crate::ser::{WritingError, serializer};
|
||||
use crate::ser::{NetworkReadExt, ReadingError, WritingError, deserializer, serializer};
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::{CustomNameImpl, DataComponentImpl};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_id_remap::{remap_item_id_for_version, remap_item_id_from_version};
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{
|
||||
Deserialize, Serialize, Serializer,
|
||||
de::{self, SeqAccess},
|
||||
de::{self, DeserializeSeed, SeqAccess},
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ItemStackSerializer<'a>(pub Cow<'a, ItemStack>);
|
||||
@@ -63,6 +67,70 @@ fn serialize_item_stack_with_id<S: Serializer>(
|
||||
}
|
||||
}
|
||||
|
||||
struct ComponentAccess<R: Read> {
|
||||
deserializer: deserializer::Deserializer<R>,
|
||||
}
|
||||
|
||||
impl<'de, R: Read> SeqAccess<'de> for ComponentAccess<R> {
|
||||
type Error = ReadingError;
|
||||
|
||||
fn next_element_seed<T: DeserializeSeed<'de>>(
|
||||
&mut self,
|
||||
seed: T,
|
||||
) -> Result<Option<T::Value>, Self::Error> {
|
||||
seed.deserialize(&mut self.deserializer).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_component_id(read: &mut impl Read) -> Result<DataComponent, ReadingError> {
|
||||
let id_val = read.get_var_int()?.0;
|
||||
let id_u8 = id_val
|
||||
.try_into()
|
||||
.map_err(|_| ReadingError::Message(format!("Invalid component ID: {id_val}")))?;
|
||||
DataComponent::try_from_id(id_u8)
|
||||
.ok_or_else(|| ReadingError::Message(format!("Unknown component ID: {id_val}")))
|
||||
}
|
||||
|
||||
fn decode_custom_name(component_data: &[u8]) -> Result<Box<dyn DataComponentImpl>, ReadingError> {
|
||||
let mut cursor = Cursor::new(component_data);
|
||||
let mut nbt_reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(&mut cursor);
|
||||
let tag = NbtTag::deserialize(&mut nbt_reader)
|
||||
.map_err(|err| ReadingError::Message(format!("Failed to decode CustomName NBT: {err}")))?;
|
||||
let name = match tag {
|
||||
NbtTag::String(name) => TextComponent::text(name.to_string()),
|
||||
NbtTag::Compound(compound) => compound
|
||||
.get_string("text")
|
||||
.map_or_else(TextComponent::empty, |name| {
|
||||
TextComponent::text(name.to_string())
|
||||
}),
|
||||
_ => TextComponent::empty(),
|
||||
};
|
||||
Ok(CustomNameImpl { name }.to_dyn())
|
||||
}
|
||||
|
||||
fn read_length_prefixed_component(
|
||||
read: &mut impl Read,
|
||||
) -> Result<(DataComponent, Box<dyn DataComponentImpl>), ReadingError> {
|
||||
let id = read_component_id(read)?;
|
||||
let byte_len = read.get_var_int()?.0;
|
||||
let byte_len = byte_len
|
||||
.try_into()
|
||||
.map_err(|_| ReadingError::Message("Negative component data length".into()))?;
|
||||
let component_data = read.read_boxed_slice(byte_len)?;
|
||||
|
||||
let component_impl = if id == DataComponent::CustomName {
|
||||
decode_custom_name(component_data.as_ref())?
|
||||
} else {
|
||||
let cursor = Cursor::new(component_data);
|
||||
let mut access = ComponentAccess {
|
||||
deserializer: deserializer::Deserializer::new(cursor),
|
||||
};
|
||||
deserialize(id, &mut access)?
|
||||
};
|
||||
|
||||
Ok((id, component_impl))
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ItemStackSerializer<'static> {
|
||||
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct Visitor;
|
||||
@@ -159,6 +227,63 @@ impl Serialize for ItemStackSerializer<'_> {
|
||||
}
|
||||
|
||||
impl ItemStackSerializer<'_> {
|
||||
pub fn read_length_prefixed_optional(
|
||||
mut read: impl Read,
|
||||
) -> Result<ItemStackSerializer<'static>, ReadingError> {
|
||||
const MAX_COMPONENTS: i32 = 256;
|
||||
|
||||
let item_count = read.get_var_int()?;
|
||||
if item_count.0 == 0 {
|
||||
return Ok(ItemStackSerializer(Cow::Borrowed(ItemStack::EMPTY)));
|
||||
}
|
||||
let item_count_u8 = item_count
|
||||
.0
|
||||
.try_into()
|
||||
.map_err(|_| ReadingError::Message("Invalid item count!".into()))?;
|
||||
|
||||
let item_id = read.get_var_int()?;
|
||||
let num_to_add = read.get_var_int()?.0;
|
||||
let num_to_remove = read.get_var_int()?.0;
|
||||
|
||||
if num_to_add < 0 || num_to_remove < 0 {
|
||||
return Err(ReadingError::Message("Negative component count".into()));
|
||||
}
|
||||
|
||||
let total_components = num_to_add
|
||||
.checked_add(num_to_remove)
|
||||
.ok_or_else(|| ReadingError::Message("Component count overflow".into()))?;
|
||||
|
||||
if total_components > MAX_COMPONENTS {
|
||||
return Err(ReadingError::Message(
|
||||
"Too many components in ItemStack patch".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut patch = Vec::with_capacity(total_components as usize);
|
||||
|
||||
for _ in 0..num_to_add {
|
||||
let (id, component_impl) = read_length_prefixed_component(&mut read)?;
|
||||
patch.push((id, Some(component_impl)));
|
||||
}
|
||||
|
||||
for _ in 0..num_to_remove {
|
||||
patch.push((read_component_id(&mut read)?, None));
|
||||
}
|
||||
|
||||
let item_id_u16 = item_id
|
||||
.0
|
||||
.try_into()
|
||||
.map_err(|_| ReadingError::Message("Invalid item id!".into()))?;
|
||||
|
||||
Ok(ItemStackSerializer(Cow::Owned(
|
||||
ItemStack::new_with_component(
|
||||
item_count_u8,
|
||||
Item::from_id(item_id_u16).unwrap_or(&Item::AIR),
|
||||
patch,
|
||||
),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn write_with_version(
|
||||
&self,
|
||||
write: impl std::io::Write,
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
use pumpkin_data::packet::serverbound::PLAY_SET_CREATIVE_MODE_SLOT;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use crate::{
|
||||
ServerPacket,
|
||||
codec::item_stack_seralizer::ItemStackSerializer,
|
||||
ser::{NetworkReadExt, ReadingError},
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[java_packet(PLAY_SET_CREATIVE_MODE_SLOT)]
|
||||
pub struct SSetCreativeSlot {
|
||||
pub slot: i16,
|
||||
pub clicked_item: ItemStackSerializer<'static>,
|
||||
}
|
||||
|
||||
impl ServerPacket for SSetCreativeSlot {
|
||||
fn read(
|
||||
mut read: impl std::io::Read,
|
||||
_version: &JavaMinecraftVersion,
|
||||
) -> Result<Self, ReadingError> {
|
||||
let slot = read.get_i16_be()?;
|
||||
let clicked_item = ItemStackSerializer::read_length_prefixed_optional(read)?;
|
||||
Ok(Self { slot, clicked_item })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::data_component_impl::CustomNameImpl;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
pub struct NameTagItem;
|
||||
|
||||
@@ -30,7 +29,7 @@ impl ItemBehaviour for NameTagItem {
|
||||
&& let Some(name) = item.get_data_component::<CustomNameImpl>()
|
||||
{
|
||||
// TODO
|
||||
entity.set_custom_name(TextComponent::text(name.name.clone()));
|
||||
entity.set_custom_name(name.name.clone());
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -293,7 +293,12 @@ impl JavaClient {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
e.log();
|
||||
error!(
|
||||
"Failed to handle play packet id {} (payload {} bytes): {}",
|
||||
packet.id,
|
||||
packet.payload.len(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,8 +246,7 @@ impl HostItemStack for PluginHostState {
|
||||
.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)?));
|
||||
return Ok(Some(self.add_text_component(name_impl.name.clone())?));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
@@ -260,19 +259,17 @@ impl HostItemStack for PluginHostState {
|
||||
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);
|
||||
let name = text_component_from_resource(self, &name_res);
|
||||
if let Some((_, data)) = stack
|
||||
.patch
|
||||
.iter_mut()
|
||||
.find(|(id, _)| *id == DataComponent::CustomName)
|
||||
{
|
||||
*data = Some(Box::new(CustomNameImpl { name: name_str }));
|
||||
*data = Some(Box::new(CustomNameImpl { name }));
|
||||
} else {
|
||||
stack.patch.push((
|
||||
DataComponent::CustomName,
|
||||
Some(Box::new(CustomNameImpl { name: name_str })),
|
||||
Some(Box::new(CustomNameImpl { name })),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user