diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2023-12-05 10:59:05 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-05 10:59:05 -0600 |
| commit | 7857a014b92e64361ee237ceae7ef1acc185ac46 (patch) | |
| tree | 5d70ea6b41943493873810e6a03c3483ff90a235 /azalea-chat/src | |
| parent | ea3e8600126a58f5666d50fbf70dff8209d8979f (diff) | |
| download | azalea-drasl-7857a014b92e64361ee237ceae7ef1acc185ac46.tar.xz | |
1.20.3 (#110)
* 23w40a
* 23w41a
* 23w42a
* 23w43a
* 23w44a
* serialize FormattedText as nbt in network
* use azalea-nbt/serde in azalea-chat
* 23w45a
* fix 23w45a to compile
* handle Object in codegen
* 1.20.3-pre2
* remove unused clientbound_resource_pack_packet.rs
* merge main and make azalea-chat use simdnbt
* 1.20.3-rc1
* fix tests
* use simdnbt 0.3
* fix ServerboundSetJigsawBlockPacket
* 1.20.3
Diffstat (limited to 'azalea-chat/src')
| -rwxr-xr-x | azalea-chat/src/base_component.rs | 20 | ||||
| -rwxr-xr-x | azalea-chat/src/component.rs | 177 | ||||
| -rwxr-xr-x | azalea-chat/src/lib.rs | 2 | ||||
| -rw-r--r-- | azalea-chat/src/numbers.rs | 52 | ||||
| -rwxr-xr-x | azalea-chat/src/style.rs | 209 | ||||
| -rwxr-xr-x | azalea-chat/src/text_component.rs | 22 | ||||
| -rwxr-xr-x | azalea-chat/src/translatable_component.rs | 60 |
7 files changed, 464 insertions, 78 deletions
diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs index dcc28ecc..8f70ecb7 100755 --- a/azalea-chat/src/base_component.rs +++ b/azalea-chat/src/base_component.rs @@ -19,6 +19,26 @@ impl BaseComponent { } } +#[cfg(feature = "simdnbt")] +impl simdnbt::Serialize for BaseComponent { + fn to_compound(self) -> simdnbt::owned::NbtCompound { + let mut compound = simdnbt::owned::NbtCompound::new(); + if !self.siblings.is_empty() { + compound.insert( + "extra", + simdnbt::owned::NbtList::from( + self.siblings + .into_iter() + .map(|component| component.to_compound()) + .collect::<Vec<_>>(), + ), + ); + } + compound.extend(self.style.to_compound()); + compound + } +} + impl Default for BaseComponent { fn default() -> Self { Self::new() diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index e80e7e4b..ded77d01 100755 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -8,11 +8,9 @@ use crate::{ use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use once_cell::sync::Lazy; use serde::{de, Deserialize, Deserializer, Serialize}; -use std::{ - fmt::Display, - io::{Cursor, Write}, -}; -use tracing::debug; +#[cfg(feature = "simdnbt")] +use simdnbt::{Deserialize as _, FromNbtTag as _, Serialize as _}; +use std::fmt::Display; /// A chat component, basically anything you can see in chat. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)] @@ -52,14 +50,21 @@ impl FormattedText { fn parse_separator( json: &serde_json::Value, ) -> Result<Option<FormattedText>, serde_json::Error> { - if json.get("separator").is_some() { - return Ok(Some(FormattedText::deserialize( - json.get("separator").unwrap(), - )?)); + if let Some(separator) = json.get("separator") { + return Ok(Some(FormattedText::deserialize(separator)?)); } Ok(None) } + #[cfg(feature = "simdnbt")] + fn parse_separator_nbt(nbt: &simdnbt::borrow::NbtCompound) -> Option<FormattedText> { + if let Some(separator) = nbt.get("separator") { + FormattedText::from_nbt_tag(separator) + } else { + None + } + } + /// Convert this component into an /// [ANSI string](https://en.wikipedia.org/wiki/ANSI_escape_code), so you /// can print it to your terminal and get styling. @@ -83,7 +88,7 @@ impl FormattedText { /// ``` pub fn to_ansi(&self) -> String { // default the default_style to white if it's not set - self.to_ansi_custom_style(&DEFAULT_STYLE) + self.to_ansi_with_custom_style(&DEFAULT_STYLE) } /// Convert this component into an @@ -91,7 +96,7 @@ impl FormattedText { /// /// This is the same as [`FormattedText::to_ansi`], but you can specify a /// default [`Style`] to use. - pub fn to_ansi_custom_style(&self, default_style: &Style) -> String { + pub fn to_ansi_with_custom_style(&self, default_style: &Style) -> String { // this contains the final string will all the ansi escape codes let mut built_string = String::new(); // this style will update as we visit components @@ -273,23 +278,153 @@ impl<'de> Deserialize<'de> for FormattedText { } } +#[cfg(feature = "simdnbt")] +impl simdnbt::Serialize for FormattedText { + fn to_compound(self) -> simdnbt::owned::NbtCompound { + match self { + FormattedText::Text(c) => c.to_compound(), + FormattedText::Translatable(c) => c.to_compound(), + } + } +} + +#[cfg(feature = "simdnbt")] +impl simdnbt::FromNbtTag for FormattedText { + fn from_nbt_tag(tag: &simdnbt::borrow::NbtTag) -> Option<Self> { + // we create a component that we might add siblings to + let mut component: FormattedText; + + match tag { + // if it's a string, return a text component with that string + simdnbt::borrow::NbtTag::String(string) => { + Some(FormattedText::Text(TextComponent::new(string.to_string()))) + } + // if it's a compound, make it do things with { text } and stuff + simdnbt::borrow::NbtTag::Compound(compound) => { + if let Some(text) = compound.get("text") { + let text = text.string().unwrap_or_default().to_string(); + component = FormattedText::Text(TextComponent::new(text)); + } else if let Some(translate) = compound.get("translate") { + let translate = translate.string()?.into(); + if let Some(with) = compound.get("with") { + let with = with.list()?.compounds()?; + let mut with_array = Vec::with_capacity(with.len()); + for item in with { + // if it's a string component with no styling and no siblings, just add + // a string to with_array otherwise add the + // component to the array + let c = FormattedText::from_nbt_tag( + &simdnbt::borrow::NbtTag::Compound(item.clone()), + )?; + if let FormattedText::Text(text_component) = c { + if text_component.base.siblings.is_empty() + && text_component.base.style.is_empty() + { + with_array.push(StringOrComponent::String(text_component.text)); + continue; + } + } + with_array.push(StringOrComponent::FormattedText( + FormattedText::from_nbt_tag(&simdnbt::borrow::NbtTag::Compound( + item.clone(), + ))?, + )); + } + component = FormattedText::Translatable(TranslatableComponent::new( + translate, with_array, + )); + } else { + // if it doesn't have a "with", just have the with_array be empty + component = FormattedText::Translatable(TranslatableComponent::new( + translate, + Vec::new(), + )); + } + } else if let Some(score) = compound.compound("score") { + // object = GsonHelper.getAsJsonObject(jsonObject, "score"); + if score.get("name").is_none() || score.get("objective").is_none() { + // A score component needs at least a name and an objective + tracing::trace!("A score component needs at least a name and an objective"); + return None; + } + // TODO, score text components aren't yet supported + return None; + } else if compound.get("selector").is_some() { + // selector text components aren't yet supported + tracing::trace!("selector text components aren't yet supported"); + return None; + } else if compound.get("keybind").is_some() { + // keybind text components aren't yet supported + tracing::trace!("keybind text components aren't yet supported"); + return None; + } else { + let Some(_nbt) = compound.get("nbt") else { + // Don't know how to turn 'nbt' into a FormattedText + return None; + }; + let _separator = FormattedText::parse_separator_nbt(compound)?; + + let _interpret = match compound.get("interpret") { + Some(v) => v.byte().unwrap_or_default() != 0, + None => false, + }; + if let Some(_block) = compound.get("block") {} + // nbt text components aren't yet supported + return None; + } + if let Some(extra) = compound.get("extra") { + let extra = extra.list()?.compounds()?; + if extra.is_empty() { + // Unexpected empty array of components + return None; + } + for extra_component in extra { + let sibling = FormattedText::from_nbt_tag( + &simdnbt::borrow::NbtTag::Compound(extra_component.clone()), + )?; + component.append(sibling); + } + } + + let style = Style::from_compound(compound).ok()?; + component.get_base_mut().style = style; + + Some(component) + } + // ok so it's not a compound, if it's a list deserialize every item + simdnbt::borrow::NbtTag::List(list) => { + let list = list.compounds()?; + let mut component = FormattedText::from_nbt_tag( + &simdnbt::borrow::NbtTag::Compound(list.get(0)?.clone()), + )?; + for i in 1..list.len() { + component.append(FormattedText::from_nbt_tag( + &simdnbt::borrow::NbtTag::Compound(list.get(i)?.clone()), + )?); + } + Some(component) + } + _ => Some(FormattedText::Text(TextComponent::new("".to_owned()))), + } + } +} + #[cfg(feature = "azalea-buf")] impl McBufReadable for FormattedText { - fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { - let string = String::read_from(buf)?; - debug!("FormattedText string: {}", string); - let json: serde_json::Value = serde_json::from_str(string.as_str())?; - let component = FormattedText::deserialize(json)?; - Ok(component) + fn read_from(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, BufReadError> { + let nbt = simdnbt::borrow::NbtTag::read(buf)?; + FormattedText::from_nbt_tag(&nbt) + .ok_or(BufReadError::Custom("couldn't read nbt".to_owned())) } } #[cfg(feature = "azalea-buf")] +#[cfg(feature = "simdnbt")] impl McBufWritable for FormattedText { - fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let json = serde_json::to_string(self).unwrap(); - json.write_into(buf)?; - Ok(()) + fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + let mut out = Vec::new(); + simdnbt::owned::BaseNbt::write_unnamed(&(self.clone().to_compound().into()), &mut out); + buf.write_all(&out) } } diff --git a/azalea-chat/src/lib.rs b/azalea-chat/src/lib.rs index d6ff7285..9995a183 100755 --- a/azalea-chat/src/lib.rs +++ b/azalea-chat/src/lib.rs @@ -2,6 +2,8 @@ pub mod base_component; mod component; +#[cfg(feature = "numbers")] +pub mod numbers; pub mod style; pub mod text_component; pub mod translatable_component; diff --git a/azalea-chat/src/numbers.rs b/azalea-chat/src/numbers.rs new file mode 100644 index 00000000..21c30591 --- /dev/null +++ b/azalea-chat/src/numbers.rs @@ -0,0 +1,52 @@ +//! Contains a few ways to style numbers. At the time of writing, Minecraft only +//! uses this for rendering scoreboard objectives. + +use std::io::{Cursor, Write}; + +#[cfg(feature = "azalea-buf")] +use azalea_buf::{McBufReadable, McBufWritable}; +use azalea_registry::NumberFormatKind; +use simdnbt::owned::Nbt; + +use crate::FormattedText; + +#[derive(Clone, Debug)] +pub enum NumberFormat { + Blank, + Styled { style: Nbt }, + Fixed { value: FormattedText }, +} + +#[cfg(feature = "azalea-buf")] +impl McBufReadable for NumberFormat { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> { + let kind = NumberFormatKind::read_from(buf)?; + match kind { + NumberFormatKind::Blank => Ok(NumberFormat::Blank), + NumberFormatKind::Styled => Ok(NumberFormat::Styled { + style: Nbt::read(buf)?, + }), + NumberFormatKind::Fixed => Ok(NumberFormat::Fixed { + value: FormattedText::read_from(buf)?, + }), + } + } +} + +#[cfg(feature = "azalea-buf")] +impl McBufWritable for NumberFormat { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + match self { + NumberFormat::Blank => NumberFormatKind::Blank.write_into(buf)?, + NumberFormat::Styled { style } => { + NumberFormatKind::Styled.write_into(buf)?; + style.write_into(buf)?; + } + NumberFormat::Fixed { value } => { + NumberFormatKind::Fixed.write_into(buf)?; + value.write_into(buf)?; + } + } + Ok(()) + } +} diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs index ba4d6e72..43b74cbf 100755 --- a/azalea-chat/src/style.rs +++ b/azalea-chat/src/style.rs @@ -5,6 +5,8 @@ use azalea_buf::McBuf; use once_cell::sync::Lazy; use serde::{ser::SerializeStruct, Serialize, Serializer}; use serde_json::Value; +#[cfg(feature = "simdnbt")] +use simdnbt::owned::{NbtCompound, NbtTag}; #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TextColor { @@ -17,11 +19,22 @@ impl Serialize for TextColor { where S: Serializer, { - if self.name.is_some() { - serializer.serialize_str(&self.name.as_ref().unwrap().to_ascii_lowercase()) - } else { - serializer.serialize_str(&self.format()) - } + serializer.serialize_str( + &self + .name + .as_ref() + .map(|n| n.to_ascii_lowercase()) + .unwrap_or_else(|| self.format()), + ) + } +} + +#[cfg(feature = "simdnbt")] +impl simdnbt::ToNbtTag for TextColor { + fn to_nbt_tag(self) -> simdnbt::owned::NbtTag { + self.name + .map(|n| NbtTag::String(n.to_ascii_lowercase().into())) + .unwrap_or_else(|| NbtTag::Int(self.value as i32)) } } @@ -303,6 +316,36 @@ pub struct Style { pub reset: bool, } +fn serde_serialize_field<S: serde::ser::SerializeStruct>( + state: &mut S, + name: &'static str, + value: &Option<impl serde::Serialize>, + default: &(impl serde::Serialize + ?Sized), + reset: bool, +) -> Result<(), S::Error> { + if let Some(value) = value { + state.serialize_field(name, value)?; + } else if reset { + state.serialize_field(name, default)?; + } + Ok(()) +} + +#[cfg(feature = "simdnbt")] +fn simdnbt_serialize_field( + compound: &mut simdnbt::owned::NbtCompound, + name: &'static str, + value: Option<impl simdnbt::ToNbtTag>, + default: impl simdnbt::ToNbtTag, + reset: bool, +) { + if let Some(value) = value { + compound.insert(name, value); + } else if reset { + compound.insert(name, default); + } +} + impl Serialize for Style { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where @@ -319,69 +362,97 @@ impl Serialize for Style { + usize::from(self.obfuscated.is_some()) }; let mut state = serializer.serialize_struct("Style", len)?; - if let Some(color) = &self.color { - state.serialize_field("color", color)?; - } else if self.reset { - state.serialize_field("color", "white")?; - } - if let Some(bold) = &self.bold { - state.serialize_field("bold", bold)?; - } else if self.reset { - state.serialize_field("bold", &false)?; - } - if let Some(italic) = &self.italic { - state.serialize_field("italic", italic)?; - } else if self.reset { - state.serialize_field("italic", &false)?; - } - if let Some(underlined) = &self.underlined { - state.serialize_field("underlined", underlined)?; - } else if self.reset { - state.serialize_field("underlined", &false)?; - } - if let Some(strikethrough) = &self.strikethrough { - state.serialize_field("strikethrough", strikethrough)?; - } else if self.reset { - state.serialize_field("strikethrough", &false)?; - } - if let Some(obfuscated) = &self.obfuscated { - state.serialize_field("obfuscated", obfuscated)?; - } else if self.reset { - state.serialize_field("obfuscated", &false)?; - } + + serde_serialize_field(&mut state, "color", &self.color, "white", self.reset)?; + serde_serialize_field(&mut state, "bold", &self.bold, &false, self.reset)?; + serde_serialize_field(&mut state, "italic", &self.italic, &false, self.reset)?; + serde_serialize_field( + &mut state, + "underlined", + &self.underlined, + &false, + self.reset, + )?; + serde_serialize_field( + &mut state, + "strikethrough", + &self.strikethrough, + &false, + self.reset, + )?; + serde_serialize_field( + &mut state, + "obfuscated", + &self.obfuscated, + &false, + self.reset, + )?; + state.end() } } +#[cfg(feature = "simdnbt")] +impl simdnbt::Serialize for Style { + fn to_compound(self) -> NbtCompound { + let mut compound = NbtCompound::new(); + + simdnbt_serialize_field(&mut compound, "color", self.color, "white", self.reset); + simdnbt_serialize_field(&mut compound, "bold", self.bold, false, self.reset); + simdnbt_serialize_field(&mut compound, "italic", self.italic, false, self.reset); + simdnbt_serialize_field( + &mut compound, + "underlined", + self.underlined, + false, + self.reset, + ); + simdnbt_serialize_field( + &mut compound, + "strikethrough", + self.strikethrough, + false, + self.reset, + ); + simdnbt_serialize_field( + &mut compound, + "obfuscated", + self.obfuscated, + false, + self.reset, + ); + + compound + } +} + impl Style { pub fn empty() -> Self { Self::default() } pub fn deserialize(json: &Value) -> Style { - return if json.is_object() { - let json_object = json.as_object().unwrap(); - let bold = json_object.get("bold").and_then(|v| v.as_bool()); - let italic = json_object.get("italic").and_then(|v| v.as_bool()); - let underlined = json_object.get("underlined").and_then(|v| v.as_bool()); - let strikethrough = json_object.get("strikethrough").and_then(|v| v.as_bool()); - let obfuscated = json_object.get("obfuscated").and_then(|v| v.as_bool()); - let color: Option<TextColor> = json_object - .get("color") - .and_then(|v| v.as_str()) - .and_then(|v| TextColor::parse(v.to_string())); - Style { - color, - bold, - italic, - underlined, - strikethrough, - obfuscated, - ..Style::default() - } - } else { - Style::default() + let Some(json_object) = json.as_object() else { + return Style::default(); }; + let bold = json_object.get("bold").and_then(|v| v.as_bool()); + let italic = json_object.get("italic").and_then(|v| v.as_bool()); + let underlined = json_object.get("underlined").and_then(|v| v.as_bool()); + let strikethrough = json_object.get("strikethrough").and_then(|v| v.as_bool()); + let obfuscated = json_object.get("obfuscated").and_then(|v| v.as_bool()); + let color: Option<TextColor> = json_object + .get("color") + .and_then(|v| v.as_str()) + .and_then(|v| TextColor::parse(v.to_string())); + Style { + color, + bold, + italic, + underlined, + strikethrough, + obfuscated, + ..Style::default() + } } /// Check if a style has no attributes set @@ -507,6 +578,30 @@ impl Style { } } +impl simdnbt::Deserialize for Style { + fn from_compound( + compound: &simdnbt::borrow::NbtCompound, + ) -> Result<Self, simdnbt::DeserializeError> { + let bold = compound.byte("bold").map(|v| v != 0); + let italic = compound.byte("italic").map(|v| v != 0); + let underlined = compound.byte("underlined").map(|v| v != 0); + let strikethrough = compound.byte("strikethrough").map(|v| v != 0); + let obfuscated = compound.byte("obfuscated").map(|v| v != 0); + let color: Option<TextColor> = compound + .string("color") + .and_then(|v| TextColor::parse(v.to_string())); + Ok(Style { + color, + bold, + italic, + underlined, + strikethrough, + obfuscated, + ..Style::default() + }) + } +} + #[cfg(test)] mod tests { use crate::component::DEFAULT_STYLE; diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs index fefd2cb8..6f95840d 100755 --- a/azalea-chat/src/text_component.rs +++ b/azalea-chat/src/text_component.rs @@ -24,6 +24,28 @@ impl Serialize for TextComponent { } } +#[cfg(feature = "simdnbt")] +impl simdnbt::Serialize for TextComponent { + fn to_compound(self) -> simdnbt::owned::NbtCompound { + let mut compound = simdnbt::owned::NbtCompound::new(); + compound.insert("text", self.text); + compound.extend(self.base.style.to_compound()); + if !self.base.siblings.is_empty() { + compound.insert( + "extra", + simdnbt::owned::NbtList::from( + self.base + .siblings + .into_iter() + .map(|component| component.to_compound()) + .collect::<Vec<_>>(), + ), + ); + } + compound + } +} + const LEGACY_FORMATTING_CODE_SYMBOL: char = 'ยง'; /// Convert a legacy color code string into a FormattedText diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs index 56c6507e..912271ae 100755 --- a/azalea-chat/src/translatable_component.rs +++ b/azalea-chat/src/translatable_component.rs @@ -4,6 +4,8 @@ use crate::{ base_component::BaseComponent, style::Style, text_component::TextComponent, FormattedText, }; use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer}; +#[cfg(feature = "simdnbt")] +use simdnbt::Serialize as _; #[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)] #[serde(untagged)] @@ -12,6 +14,16 @@ pub enum StringOrComponent { FormattedText(FormattedText), } +#[cfg(feature = "simdnbt")] +impl simdnbt::ToNbtTag for StringOrComponent { + fn to_nbt_tag(self) -> simdnbt::owned::NbtTag { + match self { + StringOrComponent::String(s) => s.to_nbt_tag(), + StringOrComponent::FormattedText(c) => c.to_nbt_tag(), + } + } +} + /// A message whose content depends on the client's language. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TranslatableComponent { @@ -33,6 +45,54 @@ impl Serialize for TranslatableComponent { } } +#[cfg(feature = "simdnbt")] +fn serialize_args_as_nbt(args: &[StringOrComponent]) -> simdnbt::owned::NbtList { + // if it's all strings then make it a string list + // if it's all components then make it a compound list + // if it's a mix then return an error + + let mut string_list = Vec::new(); + let mut compound_list = Vec::new(); + + for arg in args { + match arg { + StringOrComponent::String(s) => { + string_list.push(s.clone()); + } + StringOrComponent::FormattedText(c) => { + compound_list.push(c.clone().to_compound()); + } + } + } + + if !string_list.is_empty() && !compound_list.is_empty() { + // i'm actually not sure what vanilla does here, so i just made it return the + // string list + tracing::debug!( + "Tried to serialize a TranslatableComponent with a mix of strings and components." + ); + return string_list.into(); + } + + if !string_list.is_empty() { + return string_list.into(); + } + + compound_list.into() +} + +#[cfg(feature = "simdnbt")] +impl simdnbt::Serialize for TranslatableComponent { + fn to_compound(self) -> simdnbt::owned::NbtCompound { + let mut compound = simdnbt::owned::NbtCompound::new(); + compound.insert("translate", self.key); + compound.extend(self.base.style.to_compound()); + + compound.insert("with", serialize_args_as_nbt(&self.args)); + compound + } +} + impl TranslatableComponent { pub fn new(key: String, args: Vec<StringOrComponent>) -> Self { Self { |
