aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/chat.rs1079
-rw-r--r--src/lib.rs1
-rw-r--r--src/test_macros.rs12
-rw-r--r--src/types.rs314
-rw-r--r--src/uuid.rs15
-rw-r--r--src/v1_15_2.rs437
6 files changed, 1293 insertions, 565 deletions
diff --git a/src/chat.rs b/src/chat.rs
new file mode 100644
index 0000000..be4d5b5
--- /dev/null
+++ b/src/chat.rs
@@ -0,0 +1,1079 @@
+use std::{fmt, str};
+use serde::{Serialize, Deserialize, Deserializer, de, Serializer};
+use serde::de::{Visitor, Error, IntoDeserializer, MapAccess};
+use std::collections::BTreeMap;
+use serde::ser::SerializeMap;
+use serde_json::Value;
+use crate::{SerializeResult, DeserializeResult};
+
+pub type BoxedChat = Box<Chat>;
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum Chat {
+ Text(TextComponent),
+ Translation(TranslationComponent),
+ Keybind(KeybindComponent),
+ Score(ScoreComponent),
+}
+
+impl Chat {
+ pub fn base(&self) -> &BaseComponent {
+ use Chat::*;
+
+ match self {
+ Text(body) => &body.base,
+ Translation(body) => &body.base,
+ Keybind(body) => &body.base,
+ Score(body) => &body.base,
+ }
+ }
+
+ pub fn siblings(&self) -> &Vec<BoxedChat> {
+ &self.base().extra
+ }
+
+ pub fn boxed(self) -> BoxedChat {
+ Box::new(self)
+ }
+
+ pub fn from_text(text: &str) -> Chat {
+ Chat::Text(TextComponent {
+ base: BaseComponent::default(),
+ text: text.to_owned(),
+ })
+ }
+
+ pub fn from_traditional(orig: &str, translate_colorcodes: bool) -> Chat {
+ TraditionalParser::new(orig, translate_colorcodes).parse()
+ }
+
+ pub fn to_traditional(&self) -> Option<String> {
+ use Chat::*;
+
+ match self {
+ Text(body) => Some(body.to_traditional()),
+ _ => None
+ }
+ }
+}
+
+struct TraditionalParser {
+ source: Vec<char>,
+ at: usize,
+ translate_colorcodes: bool,
+
+ // state
+ text: String,
+ color: Option<ColorCode>,
+ bold: bool,
+ italic: bool,
+ underlined: bool,
+ strikethrough: bool,
+ obfuscated: bool,
+
+ // all the parts we've already seen
+ done: Vec<TextComponent>,
+}
+
+impl TraditionalParser {
+
+ fn new(source: &str, translate_colorcodes: bool) -> TraditionalParser {
+ Self {
+ source: source.chars().collect(),
+ at: 0,
+ translate_colorcodes,
+
+ text: String::new(),
+ color: None,
+ bold: false,
+ italic: false,
+ underlined: false,
+ strikethrough: false,
+ obfuscated: false,
+
+ done: Vec::new(),
+ }
+ }
+
+ fn parse(mut self) -> Chat {
+ loop {
+ if let Some(formatter) = self.consume_formatter() {
+ self.handle_formatter(formatter)
+ } else if let Some(next) = self.consume_char() {
+ self.push_next(next)
+ } else {
+ return self.finalize()
+ }
+ }
+ }
+
+ fn handle_formatter(&mut self, formatter: Formatter) {
+ use Formatter::*;
+
+ if self.has_text() {
+ self.finish_current();
+ }
+
+ match formatter {
+ Color(color) => {
+ self.finish_current();
+ self.color = Some(color);
+ }
+ Obfuscated => self.obfuscated = true,
+ Bold => self.bold = true,
+ Strikethrough => self.strikethrough = true,
+ Underline => self.underlined = true,
+ Italic => self.italic = true,
+ _ => {}
+ }
+ }
+
+ fn push_next(&mut self, next: char) {
+ self.text.push(next);
+ }
+
+ fn finish_current(&mut self) {
+ if self.has_text() {
+ let current = TextComponent {
+ text: self.text.clone(),
+ base: BaseComponent {
+ color: self.color.clone(),
+ bold: self.bold,
+ italic: self.italic,
+ underlined: self.underlined,
+ strikethrough: self.strikethrough,
+ obfuscated: self.obfuscated,
+ hover_event: None,
+ click_event: None,
+ insertion: None,
+ extra: Vec::default()
+ }
+ };
+ self.text.clear();
+ self.done.push(current);
+ }
+
+ self.reset_style();
+ }
+
+ fn reset_style(&mut self) {
+ self.bold = false;
+ self.italic = false;
+ self.underlined = false;
+ self.strikethrough = false;
+ self.obfuscated = false;
+ self.color = None;
+ }
+
+ fn has_text(&self) -> bool {
+ return !self.text.is_empty()
+ }
+
+ fn is_on_formatter(&self) -> bool {
+ self.source.get(self.at).map(move |c| {
+ let c = *c;
+ c == SECTION_SYMBOL || (self.translate_colorcodes && c == '&')
+ }).unwrap_or(false)
+ }
+
+ fn consume_char(&mut self) -> Option<char> {
+ if let Some(c) = self.source.get(self.at) {
+ self.at += 1;
+ Some(*c)
+ } else {
+ None
+ }
+ }
+
+ fn consume_formatter(&mut self) -> Option<Formatter> {
+ if self.is_on_formatter() {
+ self.consume_char()?;
+ let c = self.consume_char()?;
+ let out = Formatter::from_code(&c);
+ if out.is_none() {
+ self.at -= 1;
+ }
+
+ out
+ } else {
+ None
+ }
+ }
+
+ fn finalize(mut self) -> Chat {
+ self.finish_current();
+ self.simplify();
+ let n_components = self.done.len();
+ if n_components == 1 {
+ return Chat::Text(self.done.remove(0));
+ }
+
+ let mut top_level = TextComponent {
+ text: String::default(),
+ base: BaseComponent::default(),
+ };
+
+ if n_components > 0 {
+ top_level.base.extra.extend(
+ self.done.into_iter()
+ .map(move |component| Chat::Text(component).boxed()));
+ }
+
+ Chat::Text(top_level)
+ }
+
+ fn simplify(&mut self) {
+ let mut updated = Vec::with_capacity(self.done.len());
+ let mut last: Option<TextComponent> = None;
+ while !self.done.is_empty() {
+ let cur = self.done.remove(0);
+ if let Some(mut la) = last.take() {
+ if la.base.has_same_style_as(&cur.base) {
+ la.text.extend(cur.text.chars());
+ last = Some(la);
+ continue;
+ } else {
+ updated.push(la);
+ }
+ }
+
+ last = Some(cur);
+ }
+
+ if let Some(l) = last.take() {
+ updated.push(l);
+ }
+
+ self.done = updated;
+ }
+}
+
+#[derive(Serialize, Clone, Debug, PartialEq)]
+pub struct BaseComponent {
+ #[serde(skip_serializing_if = "should_skip_flag_field")]
+ pub bold: bool,
+ #[serde(skip_serializing_if = "should_skip_flag_field")]
+ pub italic: bool,
+ #[serde(skip_serializing_if = "should_skip_flag_field")]
+ pub underlined: bool,
+ #[serde(skip_serializing_if = "should_skip_flag_field")]
+ pub strikethrough: bool,
+ #[serde(skip_serializing_if = "should_skip_flag_field")]
+ pub obfuscated: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub color: Option<ColorCode>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub insertion: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub click_event: Option<ChatClickEvent>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub hover_event: Option<ChatHoverEvent>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub extra: Vec<BoxedChat>,
+}
+
+fn should_skip_flag_field(flag: &bool) -> bool {
+ !*flag
+}
+
+impl BaseComponent {
+
+ fn has_same_style_as(&self, other: &Self) -> bool {
+ other.bold == self.bold &&
+ other.italic == self.italic &&
+ other.underlined == self.underlined &&
+ other.strikethrough == self.strikethrough &&
+ other.obfuscated == self.obfuscated &&
+ other.color.eq(&self.color)
+ }
+}
+
+impl Into<BaseComponent> for JsonComponentBase {
+ fn into(self) -> BaseComponent {
+ BaseComponent {
+ bold: self.bold.unwrap_or(false),
+ italic: self.italic.unwrap_or(false),
+ underlined: self.underlined.unwrap_or(false),
+ strikethrough: self.strikethrough.unwrap_or(false),
+ obfuscated: self.obfuscated.unwrap_or(false),
+ color: self.color,
+ insertion: self.insertion,
+ click_event: self.click_event,
+ hover_event: self.hover_event,
+ extra: self.extra.into_iter().map(move |elem| elem.boxed()).collect(),
+ }
+ }
+}
+
+#[derive(Deserialize)]
+struct JsonComponentBase {
+ pub bold: Option<bool>,
+ pub italic: Option<bool>,
+ pub underlined: Option<bool>,
+ pub strikethrough: Option<bool>,
+ pub obfuscated: Option<bool>,
+ pub color: Option<ColorCode>,
+ pub insertion: Option<String>,
+ #[serde(rename = "clickEvent")]
+ pub click_event: Option<ChatClickEvent>,
+ #[serde(rename = "hoverEvent")]
+ pub hover_event: Option<ChatHoverEvent>,
+ #[serde(default = "Vec::default")]
+ pub extra: Vec<Chat>,
+
+ #[serde(flatten)]
+ _additional: BTreeMap<String, serde_json::Value>
+}
+
+impl Default for BaseComponent {
+ fn default() -> Self {
+ Self {
+ bold: false,
+ italic: false,
+ underlined: false,
+ strikethrough: false,
+ obfuscated: false,
+ color: None,
+ insertion: None,
+ click_event: None,
+ hover_event: None,
+ extra: Vec::default(),
+ }
+ }
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub struct TextComponent {
+ pub text: String,
+
+ #[serde(flatten)]
+ #[serde(skip_deserializing)]
+ pub base: BaseComponent,
+}
+
+impl TextComponent {
+ pub fn to_traditional(&self) -> String {
+ let b = &self.base;
+ let text = &self.text;
+
+ let (mut buf, has_formatters) = if !text.is_empty() {
+ let formatters = self.traditional_formatters(false);
+ let has_formatters = formatters.is_some();
+ let mut buf = formatters.unwrap_or_else(|| String::new());
+ buf.extend(text.chars());
+ (buf, has_formatters)
+ } else {
+ (String::default(), false)
+ };
+
+ let mut last_had_formatters = has_formatters;
+ for extra in b.extra.iter() {
+ if let Chat::Text(child) = extra.as_ref() {
+ match child.traditional_formatters(last_had_formatters) {
+ Some(child_fmts) => {
+ last_had_formatters = true;
+ buf.extend(child_fmts.chars())
+ },
+ None => {
+ last_had_formatters = false;
+ }
+ }
+
+ buf.extend(child.text.chars());
+ }
+ }
+
+ buf
+ }
+
+ fn traditional_formatters(&self, prev_colored: bool) -> Option<String> {
+ let b = &self.base;
+ let mut buf = String::default();
+
+ if let Some(c) = b.color {
+ buf.push(SECTION_SYMBOL);
+ buf.push(c.code());
+ }
+
+ let mut apply_formatter = |b: bool, formatter: Formatter| {
+ if b {
+ buf.push(SECTION_SYMBOL);
+ buf.push(formatter.code());
+ }
+ };
+
+ apply_formatter(b.bold, Formatter::Bold);
+ apply_formatter(b.italic, Formatter::Italic);
+ apply_formatter(b.strikethrough, Formatter::Strikethrough);
+ apply_formatter(b.underlined, Formatter::Underline);
+ apply_formatter(b.obfuscated, Formatter::Obfuscated);
+
+ if buf.is_empty() {
+ if prev_colored {
+ buf.push(SECTION_SYMBOL);
+ buf.push('r');
+ Some(buf)
+ } else {
+ None
+ }
+ } else {
+ Some(buf)
+ }
+ }
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub struct TranslationComponent {
+ pub translate: String,
+ pub with: Vec<BoxedChat>,
+
+ #[serde(flatten)]
+ #[serde(skip_deserializing)]
+ pub base: BaseComponent,
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub struct KeybindComponent {
+ pub keybind: String,
+
+ #[serde(flatten)]
+ #[serde(skip_deserializing)]
+ pub base: BaseComponent
+}
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub struct ScoreComponent {
+ pub score: ScoreComponentObjective,
+
+ #[serde(flatten)]
+ #[serde(skip_deserializing)]
+ pub base: BaseComponent
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub struct ScoreComponentObjective {
+ pub name: String,
+ pub objective: Option<String>,
+ pub value: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum ChatClickEvent {
+ OpenUrl(String),
+ RunCommand(String),
+ SuggestCommand(String),
+ ChangePage(i32)
+}
+
+impl Serialize for ChatClickEvent {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+ S: Serializer
+ {
+ let mut m = serializer.serialize_map(Some(2))?;
+
+ use ChatClickEvent::*;
+
+ m.serialize_entry("action", match self {
+ OpenUrl(_) => "open_url",
+ RunCommand(_) => "run_command",
+ SuggestCommand(_) => "suggest_command",
+ ChangePage(_) => "change_page",
+ })?;
+
+ m.serialize_key("value")?;
+
+ match self {
+ OpenUrl(body) => m.serialize_value(body),
+ RunCommand(body) => m.serialize_value(body),
+ SuggestCommand(body) => m.serialize_value(body),
+ ChangePage(body) => m.serialize_value(body),
+ }?;
+
+ m.end()
+ }
+}
+
+impl<'de> Deserialize<'de> for ChatClickEvent {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
+ D: Deserializer<'de>
+ {
+ struct V;
+
+ impl<'de> Visitor<'de> for V {
+ type Value = ChatClickEvent;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "an event object for ChatClickEvent")
+ }
+
+ fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error> where
+ A: MapAccess<'de>
+ {
+ let mut action: Option<&str> = None;
+ let mut value: Option<Value> = None;
+ while action.is_none() || value.is_none() {
+ if let Some(key) = map.next_key()? {
+ match key {
+ "action" => {
+ action = map.next_value()?;
+ if action.is_none() {
+ return Err(A::Error::custom("none for value key=action"));
+ }
+ },
+ "value" => {
+ value = map.next_value()?;
+ if value.is_none() {
+ return Err(A::Error::custom("none for value key=value"));
+ }
+ },
+ other => {
+ return Err(A::Error::custom(format!("unexpected key in event {}", other)));
+ }
+ }
+ } else {
+ return Err(A::Error::custom(format!("event needs action and value")));
+ }
+ }
+
+ use ChatClickEvent::*;
+ let v = value.expect("set this in while loop");
+ match action.expect("set this in while loop") {
+ "open_url" => match v.as_str() {
+ Some(url) => Ok(OpenUrl(url.to_owned())),
+ None => Err(A::Error::custom(format!("open_url requires string body, got {}", v)))
+ },
+ "run_command" => match v.as_str() {
+ Some(cmd) => Ok(RunCommand(cmd.to_owned())),
+ None => Err(A::Error::custom(format!("run_command requires string body, got {}", v)))
+ },
+ "suggest_command" => match v.as_str() {
+ Some(cmd) => Ok(SuggestCommand(cmd.to_owned())),
+ None => Err(A::Error::custom(format!("suggest_command requires string body, got {}", v)))
+ },
+ "change_page" => match v.as_i64() {
+ Some(v) => Ok(ChangePage(v as i32)),
+ None => Err(A::Error::custom(format!("change_page requires integer body, got {}", v)))
+ },
+ other => Err(A::Error::custom(format!("invalid click action kind {}", other)))
+ }
+ }
+ }
+
+ deserializer.deserialize_map(V)
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum ChatHoverEvent {
+ ShowText(BoxedChat),
+ ShowItem(Value),
+ ShowEntity(Value)
+}
+
+impl Serialize for ChatHoverEvent {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+ S: Serializer
+ {
+ let mut m = serializer.serialize_map(Some(2))?;
+
+ use ChatHoverEvent::*;
+
+ m.serialize_entry("action", match self {
+ ShowText(_) => "show_text",
+ ShowItem(_) => "show_item",
+ ShowEntity(_) => "show_entity",
+ })?;
+
+ m.serialize_key("value")?;
+
+ match self {
+ ShowText(body) => m.serialize_value(body),
+ ShowItem(body) => m.serialize_value(body),
+ ShowEntity(body) => m.serialize_value(body),
+ }?;
+
+ m.end()
+ }
+}
+
+impl<'de> Deserialize<'de> for ChatHoverEvent {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
+ D: Deserializer<'de>
+ {
+ struct V;
+
+ impl<'de> Visitor<'de> for V {
+ type Value = ChatHoverEvent;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "an event object for ChatClickEvent")
+ }
+
+ //noinspection ALL
+ fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error> where
+ A: MapAccess<'de>
+ {
+ let mut action: Option<&str> = None;
+ let mut value: Option<Value> = None;
+ while action.is_none() || value.is_none() {
+ if let Some(key) = map.next_key()? {
+ match key {
+ "action" => {
+ action = map.next_value()?;
+ if action.is_none() {
+ return Err(A::Error::custom("none for value key=action"));
+ }
+ },
+ "value" => {
+ value = map.next_value()?;
+ if value.is_none() {
+ return Err(A::Error::custom("none for value key=value"));
+ }
+ },
+ other => {
+ return Err(A::Error::custom(format!("unexpected key in event {}", other)));
+ }
+ }
+ } else {
+ return Err(A::Error::custom(format!("event needs action and value")));
+ }
+ }
+
+ use ChatHoverEvent::*;
+ let v = value.expect("set this in while loop");
+ match action.expect("set this in while loop") {
+ "show_text" => Ok(ShowText(
+ Chat::deserialize(v.into_deserializer())
+ .map_err(move |err| A::Error::custom(
+ format!("error deserializing text to show {:?}", err)))?
+ .boxed())),
+ "show_item" => Ok(ShowItem(v)),
+ "show_entity" => Ok(ShowEntity(v)),
+ other => Err(A::Error::custom(format!("invalid hover action kind {}", other)))
+ }
+ }
+ }
+
+ deserializer.deserialize_map(V)
+ }
+}
+
+pub const SECTION_SYMBOL: char = '§';
+
+#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
+pub enum ColorCode {
+ Black,
+ DarkBlue,
+ DarkGreen,
+ DarkAqua,
+ DarkRed,
+ DarkPurple,
+ Gold,
+ Gray,
+ DarkGray,
+ Blue,
+ Green,
+ Aqua,
+ Red,
+ LightPurple,
+ Yellow,
+ White,
+}
+
+impl ColorCode {
+ pub fn from_code(i: &char) -> Option<Self> {
+ match i {
+ '0' => Some(ColorCode::Black),
+ '1' => Some(ColorCode::DarkBlue),
+ '2' => Some(ColorCode::DarkGreen),
+ '3' => Some(ColorCode::DarkAqua),
+ '4' => Some(ColorCode::DarkRed),
+ '5' => Some(ColorCode::DarkPurple),
+ '6' => Some(ColorCode::Gold),
+ '7' => Some(ColorCode::Gray),
+ '8' => Some(ColorCode::DarkGray),
+ '9' => Some(ColorCode::Blue),
+ 'a' => Some(ColorCode::Green),
+ 'b' => Some(ColorCode::Aqua),
+ 'c' => Some(ColorCode::Red),
+ 'd' => Some(ColorCode::LightPurple),
+ 'e' => Some(ColorCode::Yellow),
+ 'f' => Some(ColorCode::White),
+ _ => None,
+ }
+ }
+
+ pub fn code(&self) -> char {
+ match self {
+ ColorCode::Black => '0',
+ ColorCode::DarkBlue => '1',
+ ColorCode::DarkGreen => '2',
+ ColorCode::DarkAqua => '3',
+ ColorCode::DarkRed => '4',
+ ColorCode::DarkPurple => '5',
+ ColorCode::Gold => '6',
+ ColorCode::Gray => '7',
+ ColorCode::DarkGray => '8',
+ ColorCode::Blue => '9',
+ ColorCode::Green => 'a',
+ ColorCode::Aqua => 'b',
+ ColorCode::Red => 'c',
+ ColorCode::LightPurple => 'd',
+ ColorCode::Yellow => 'e',
+ ColorCode::White => 'f',
+ }
+ }
+
+ pub fn from_name(name: &str) -> Option<Self> {
+ match name.to_ascii_lowercase().as_str() {
+ "black" => Some(ColorCode::Black),
+ "dark_blue" => Some(ColorCode::DarkBlue),
+ "dark_green" => Some(ColorCode::DarkGreen),
+ "dark_aqua" => Some(ColorCode::DarkAqua),
+ "dark_red" => Some(ColorCode::DarkRed),
+ "dark_purple" => Some(ColorCode::DarkPurple),
+ "gold" => Some(ColorCode::Gold),
+ "gray" => Some(ColorCode::Gray),
+ "dark_gray" => Some(ColorCode::DarkGray),
+ "blue" => Some(ColorCode::Blue),
+ "green" => Some(ColorCode::Green),
+ "aqua" => Some(ColorCode::Aqua),
+ "red" => Some(ColorCode::Red),
+ "light_purple" => Some(ColorCode::LightPurple),
+ "yellow" => Some(ColorCode::Yellow),
+ "white" => Some(ColorCode::White),
+ _ => None,
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match self {
+ ColorCode::Black => "black",
+ ColorCode::DarkBlue => "dark_blue",
+ ColorCode::DarkGreen => "dark_green",
+ ColorCode::DarkAqua => "dark_aqua",
+ ColorCode::DarkRed => "dark_red",
+ ColorCode::DarkPurple => "dark_purple",
+ ColorCode::Gold => "gold",
+ ColorCode::Gray => "gray",
+ ColorCode::DarkGray => "dark_gray",
+ ColorCode::Blue => "blue",
+ ColorCode::Green => "green",
+ ColorCode::Aqua => "aqua",
+ ColorCode::Red => "red",
+ ColorCode::LightPurple => "light_purple",
+ ColorCode::Yellow => "yellow",
+ ColorCode::White => "white",
+ }
+ }
+}
+
+impl fmt::Display for ColorCode {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}{}", SECTION_SYMBOL, self.code())
+ }
+}
+
+impl Serialize for ColorCode {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+ S: Serializer {
+ serializer.serialize_str(self.name())
+ }
+}
+
+impl<'de> Deserialize<'de> for ColorCode {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
+ D: Deserializer<'de>
+ {
+ struct V;
+
+ impl<'de> Visitor<'de> for V {
+ type Value = ColorCode;
+
+ fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(fmt, "a string representing a color code")
+ }
+
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
+ E: Error, {
+ if let Some(code) = ColorCode::from_name(v) {
+ Ok(code)
+ } else {
+ Err(E::custom(format!("invalid color code name {}", v)))
+ }
+ }
+ }
+
+ deserializer.deserialize_str(V)
+ }
+}
+
+#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
+pub enum Formatter {
+ Color(ColorCode),
+ Obfuscated,
+ Bold,
+ Strikethrough,
+ Underline,
+ Italic,
+ Reset,
+}
+
+impl Formatter {
+ pub fn from_code(i: &char) -> Option<Self> {
+ match i.to_ascii_lowercase() {
+ 'k' => Some(Formatter::Obfuscated),
+ 'l' => Some(Formatter::Bold),
+ 'm' => Some(Formatter::Strikethrough),
+ 'n' => Some(Formatter::Underline),
+ 'o' => Some(Formatter::Italic),
+ 'r' => Some(Formatter::Reset),
+ _ => ColorCode::from_code(i).map(Formatter::Color),
+ }
+ }
+
+ pub fn code(&self) -> char {
+ match self {
+ Formatter::Color(c) => c.code(),
+ Formatter::Obfuscated => 'k',
+ Formatter::Bold => 'l',
+ Formatter::Strikethrough => 'm',
+ Formatter::Underline => 'n',
+ Formatter::Italic => 'o',
+ Formatter::Reset => 'r',
+ }
+ }
+
+ pub fn from_name(name: &str) -> Option<Self> {
+ match name.to_ascii_lowercase().as_str() {
+ "obfuscated" => Some(Formatter::Obfuscated),
+ "bold" => Some(Formatter::Bold),
+ "strikethrough" => Some(Formatter::Strikethrough),
+ "underline" => Some(Formatter::Underline),
+ "italic" => Some(Formatter::Italic),
+ "reset" => Some(Formatter::Reset),
+ _ => ColorCode::from_name(name).map(Formatter::Color),
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match self {
+ Formatter::Obfuscated => "obfuscated",
+ Formatter::Bold => "bold",
+ Formatter::Strikethrough => "strikethrough",
+ Formatter::Underline => "underline",
+ Formatter::Italic => "italic",
+ Formatter::Reset => "reset",
+ Formatter::Color(c) => c.name(),
+ }
+ }
+}
+
+impl fmt::Display for Formatter {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}{}", SECTION_SYMBOL, self.code())
+ }
+}
+
+
+impl<'de> Deserialize<'de> for Chat {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
+ D: Deserializer<'de>
+ {
+ struct V;
+
+ impl<'de> Visitor<'de> for V {
+ type Value = Chat;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "any primitive or a JSON object specifying the component")
+ }
+
+ fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> where E: de::Error {
+ self.visit_string(value.to_string())
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> where E: de::Error {
+ self.visit_string(value.to_string())
+ }
+
+ fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> where E: de::Error {
+ self.visit_string(value.to_string())
+ }
+
+ fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: de::Error {
+ Ok(Chat::Text(TextComponent {
+ base: BaseComponent::default(),
+ text: value,
+ }))
+ }
+
+ fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error> where M: de::MapAccess<'de> {
+ let mut base: JsonComponentBase = de::Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
+ let additional = &mut base._additional;
+
+ // string component
+ if let Some(raw_text) = additional.remove("text") {
+ return if let Some(text) = raw_text.as_str() {
+ additional.clear();
+ Ok(Chat::Text(TextComponent {
+ text: text.to_owned(),
+ base: base.into(),
+ }))
+ } else {
+ Err(M::Error::custom(format!("have text but it's not a string - {:?}", raw_text)))
+ };
+ }
+
+ // translate
+ if let Some(raw_translate) = additional.remove("translate") {
+ return if let Some(translate) = raw_translate.as_str() {
+ // need "with"
+ if let Some(raw_with) = additional.remove("with") {
+ if let Some(withs) = raw_with.as_array() {
+ let mut withs_out = Vec::with_capacity(withs.len());
+ for with in withs {
+ withs_out.push(with.deserialize_any(V)
+ .map_err(move |err| M::Error::custom(
+ format!("unable to parse one of the translation with entries :: {}", err)))?
+ .boxed());
+ }
+ Ok(Chat::Translation(TranslationComponent{
+ base: base.into(),
+ translate: translate.to_owned(),
+ with: withs_out,
+ }))
+ } else {
+ Err(M::Error::custom(format!("have with but it's not an array - {:?}", raw_with)))
+ }
+ } else {
+ Err(M::Error::custom("have 'translate' but missing 'with', cannot parse"))
+ }
+ } else {
+ Err(M::Error::custom(format!("have translate but it's not a string - {:?}", raw_translate)))
+ }
+ }
+
+ // keybind
+ if let Some(raw_keybind) = additional.remove("keybind") {
+ return if let Some(keybind) = raw_keybind.as_str() {
+ Ok(Chat::Keybind(KeybindComponent{
+ keybind: keybind.to_owned(),
+ base: base.into()
+ }))
+ } else {
+ Err(M::Error::custom(format!("have keybind but it's not a string! {:?}", raw_keybind)))
+ }
+ }
+
+ // score
+ if let Some(raw_score) = additional.remove("score") {
+ let score = ScoreComponentObjective::deserialize(raw_score.into_deserializer())
+ .map_err(move |err| M::Error::custom(
+ format!("failed to deserialize scoreboard objective for score chat component :: {:?}", err)))?;
+
+ return Ok(Chat::Score(ScoreComponent{
+ score,
+ base: base.into(),
+ }));
+ }
+
+ // selector (SKIP)
+
+ Err(M::Error::custom("not able to parse chat component, not a valid chat component kind"))
+ }
+ }
+
+ deserializer.deserialize_any(V)
+ }
+}
+
+impl Serialize for Chat {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+ S: Serializer
+ {
+ use Chat::*;
+
+ match self {
+ Text(body) => body.serialize(serializer),
+ Translation(body) => body.serialize(serializer),
+ Keybind(body) => body.serialize(serializer),
+ Score(body) => body.serialize(serializer)
+ }
+ }
+}
+
+impl super::Serialize for Chat {
+ fn mc_serialize<S: super::Serializer>(&self, to: &mut S) -> SerializeResult {
+ serde_json::to_string(self)
+ .map_err(move |err| super::SerializeErr::FailedJsonEncode(
+ format!("error while encoding chat :: {:?} -> {:?}", self, err)))?
+ .mc_serialize(to)
+ }
+}
+
+impl super::Deserialize for Chat {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ String::mc_deserialize(data)?.try_map(move |raw| {
+ serde_json::from_str(raw.as_str()).map_err(move |err|
+ super::DeserializeErr::FailedJsonDeserialize(format!(
+ "failed to serialize chat as JSON :: {:?}", err
+ )))
+ })
+ }
+}
+
+#[cfg(test)]
+use super::protocol::TestRandom;
+
+#[cfg(test)]
+impl TestRandom for Chat {
+ fn test_gen_random() -> Self {
+ let str = String::test_gen_random();
+ Chat::from_text(str.as_str())
+ }
+}
+
+#[cfg(test)]
+pub mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test_from_traditional_simple() {
+ let out = Chat::from_traditional("&cthis &cis red, and &rthis is &e&lyellow", true);
+ assert_eq!(out, Chat::Text(TextComponent{
+ text: String::default(),
+ base: {
+ let mut b = BaseComponent::default();
+ b.extra = vec!(
+ Chat::Text(TextComponent{
+ text: "this is red, and ".to_owned(),
+ base: {
+ let mut b = BaseComponent::default();
+ b.color = Some(ColorCode::Red);
+ b
+ },
+ }).boxed(),
+ Chat::Text(TextComponent{
+ text: "this is ".to_owned(),
+ base: BaseComponent::default(),
+ }).boxed(),
+ Chat::Text(TextComponent{
+ text: "yellow".to_owned(),
+ base: {
+ let mut b = BaseComponent::default();
+ b.color = Some(ColorCode::Yellow);
+ b.bold = true;
+ b
+ }
+ }).boxed()
+ );
+ b
+ }
+ }));
+
+ let traditional = out.to_traditional().expect("is text");
+ assert_eq!(traditional.as_str(), "§cthis is red, and §rthis is §e§lyellow");
+ println!("{}", serde_json::to_string_pretty(&out).expect("should serialize fine"));
+ }
+} \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index c78683f..54f4aa3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,3 +20,4 @@ pub use serialize::*;
#[cfg(test)]
mod test_macros;
+mod chat;
diff --git a/src/test_macros.rs b/src/test_macros.rs
index 8283d30..254c3bb 100644
--- a/src/test_macros.rs
+++ b/src/test_macros.rs
@@ -19,9 +19,15 @@ macro_rules! packet_test_cases {
data: bytes.as_slice(),
};
- let deserialized =
- <$pnam>::mc_deserialize(raw_packet).expect("deserialize succeeds");
- assert_eq!(packet, deserialized);
+ let deserialized = match <$pnam>::mc_deserialize(raw_packet) {
+ Err(err) => {
+ eprintln!("expected: {:?}", packet);
+ panic!("error: {:?}", err);
+ },
+ Ok(out) => out
+ };
+ assert_eq!(packet, deserialized, "deserialize(serialize(packet)) == packet");
+ assert_eq!(packet.clone(), deserialized.clone(), "deserialized.clone() == packet.clone()")
}
}
diff --git a/src/types.rs b/src/types.rs
index 2082b56..cda622a 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -4,6 +4,8 @@ use crate::utils::*;
use crate::uuid::UUID4;
use crate::*;
+pub use super::chat::*;
+
#[cfg(test)]
use crate::protocol::TestRandom;
@@ -611,318 +613,6 @@ impl TestRandom for FixedInt {
}
}
-// chat
-#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
-pub struct Chat {
- pub text: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub bold: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub italic: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub underlined: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub strikethrough: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub obfuscated: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub color: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub extra: Option<Vec<Chat>>,
-}
-
-impl ToString for Chat {
- fn to_string(&self) -> String {
- self.extra
- .as_ref()
- .into_iter()
- .flat_map(|v| v.into_iter())
- .map(|item| item.to_string())
- .fold(self.text.clone(), |acc, v| acc + v.as_str())
- }
-}
-
-const SECTION_SYMBOL: char = '§';
-
-#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
-pub enum ColorCode {
- Black,
- DarkBlue,
- DarkGreen,
- DarkAqua,
- DarkRed,
- DarkPurple,
- Gold,
- Gray,
- DarkGray,
- Blue,
- Green,
- Aqua,
- Red,
- LightPurple,
- Yellow,
- White,
-}
-
-impl ColorCode {
- pub fn from_code(i: &char) -> Option<Self> {
- match i {
- '0' => Some(ColorCode::Black),
- '1' => Some(ColorCode::DarkBlue),
- '2' => Some(ColorCode::DarkGreen),
- '3' => Some(ColorCode::DarkAqua),
- '4' => Some(ColorCode::DarkRed),
- '5' => Some(ColorCode::DarkPurple),
- '6' => Some(ColorCode::Gold),
- '7' => Some(ColorCode::Gray),
- '8' => Some(ColorCode::DarkGray),
- '9' => Some(ColorCode::Blue),
- 'a' => Some(ColorCode::Green),
- 'b' => Some(ColorCode::Aqua),
- 'c' => Some(ColorCode::Red),
- 'd' => Some(ColorCode::LightPurple),
- 'e' => Some(ColorCode::Yellow),
- 'f' => Some(ColorCode::White),
- _ => None,
- }
- }
-
- pub fn to_code(&self) -> char {
- match self {
- ColorCode::Black => '0',
- ColorCode::DarkBlue => '1',
- ColorCode::DarkGreen => '2',
- ColorCode::DarkAqua => '3',
- ColorCode::DarkRed => '4',
- ColorCode::DarkPurple => '5',
- ColorCode::Gold => '6',
- ColorCode::Gray => '7',
- ColorCode::DarkGray => '8',
- ColorCode::Blue => '9',
- ColorCode::Green => 'a',
- ColorCode::Aqua => 'b',
- ColorCode::Red => 'c',
- ColorCode::LightPurple => 'd',
- ColorCode::Yellow => 'e',
- ColorCode::White => 'f',
- }
- }
-
- pub fn from_name(name: &str) -> Option<Self> {
- match name.to_ascii_lowercase().as_str() {
- "black" => Some(ColorCode::Black),
- "dark_blue" => Some(ColorCode::DarkBlue),
- "dark_green" => Some(ColorCode::DarkGreen),
- "dark_aqua" => Some(ColorCode::DarkAqua),
- "dark_red" => Some(ColorCode::DarkRed),
- "dark_purple" => Some(ColorCode::DarkPurple),
- "gold" => Some(ColorCode::Gold),
- "gray" => Some(ColorCode::Gray),
- "dark_gray" => Some(ColorCode::DarkGray),
- "blue" => Some(ColorCode::Blue),
- "green" => Some(ColorCode::Green),
- "aqua" => Some(ColorCode::Aqua),
- "red" => Some(ColorCode::Red),
- "light_purple" => Some(ColorCode::LightPurple),
- "yellow" => Some(ColorCode::Yellow),
- "white" => Some(ColorCode::White),
- _ => None,
- }
- }
-
- pub fn name(&self) -> &str {
- match self {
- ColorCode::Black => "black",
- ColorCode::DarkBlue => "dark_blue",
- ColorCode::DarkGreen => "dark_green",
- ColorCode::DarkAqua => "dark_aqua",
- ColorCode::DarkRed => "dark_red",
- ColorCode::DarkPurple => "dark_purple",
- ColorCode::Gold => "gold",
- ColorCode::Gray => "gray",
- ColorCode::DarkGray => "dark_gray",
- ColorCode::Blue => "blue",
- ColorCode::Green => "green",
- ColorCode::Aqua => "aqua",
- ColorCode::Red => "red",
- ColorCode::LightPurple => "light_purple",
- ColorCode::Yellow => "yellow",
- ColorCode::White => "white",
- }
- }
-}
-
-#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
-pub enum Formatter {
- Color(ColorCode),
- Obfuscated,
- Bold,
- Strikethrough,
- Underline,
- Italic,
- Reset,
-}
-
-impl Formatter {
- pub fn from_code(i: &char) -> Option<Self> {
- match i.to_ascii_lowercase() {
- 'k' => Some(Formatter::Obfuscated),
- 'l' => Some(Formatter::Bold),
- 'm' => Some(Formatter::Strikethrough),
- 'n' => Some(Formatter::Underline),
- 'o' => Some(Formatter::Italic),
- 'r' => Some(Formatter::Reset),
- _ => ColorCode::from_code(i).map(Formatter::Color),
- }
- }
-
- pub fn code(&self) -> char {
- match self {
- Formatter::Color(c) => c.to_code(),
- Formatter::Obfuscated => 'k',
- Formatter::Bold => 'l',
- Formatter::Strikethrough => 'm',
- Formatter::Underline => 'n',
- Formatter::Italic => 'o',
- Formatter::Reset => 'r',
- }
- }
-
- pub fn from_name(name: &str) -> Option<Self> {
- match name.to_ascii_lowercase().as_str() {
- "obfuscated" => Some(Formatter::Obfuscated),
- "bold" => Some(Formatter::Bold),
- "strikethrough" => Some(Formatter::Strikethrough),
- "underline" => Some(Formatter::Underline),
- "italic" => Some(Formatter::Italic),
- "reset" => Some(Formatter::Reset),
- _ => ColorCode::from_name(name).map(Formatter::Color),
- }
- }
-
- pub fn name(&self) -> &str {
- match self {
- Formatter::Obfuscated => "obfuscated",
- Formatter::Bold => "bold",
- Formatter::Strikethrough => "strikethrough",
- Formatter::Underline => "underline",
- Formatter::Italic => "italic",
- Formatter::Reset => "reset",
- Formatter::Color(c) => c.name(),
- }
- }
-}
-
-impl ToString for Formatter {
- fn to_string(&self) -> String {
- vec![SECTION_SYMBOL, self.code()].into_iter().collect()
- }
-}
-
-impl Chat {
- pub fn to_traditional(&self) -> String {
- self.to_traditional_parts(Vec::<Formatter>::new().as_ref(), None)
- }
-
- fn to_traditional_parts(
- &self,
- formatters: &Vec<Formatter>,
- color: Option<ColorCode>,
- ) -> String {
- let mut own_formatters = formatters.clone();
- Self::update_formatter(&mut own_formatters, Formatter::Bold, &self.bold);
- Self::update_formatter(&mut own_formatters, Formatter::Italic, &self.italic);
- Self::update_formatter(&mut own_formatters, Formatter::Underline, &self.underlined);
- Self::update_formatter(
- &mut own_formatters,
- Formatter::Strikethrough,
- &self.strikethrough,
- );
- Self::update_formatter(&mut own_formatters, Formatter::Obfuscated, &self.obfuscated);
-
- let own_color_option = self
- .color
- .as_ref()
- .map(String::as_str)
- .and_then(ColorCode::from_name)
- .or(color);
-
- let own_color = own_color_option
- .map(Formatter::Color)
- .map(|f| f.to_string());
-
- let own_formatter = own_formatters
- .clone()
- .into_iter()
- .map(|f| f.to_string())
- .fold(String::new(), |acc, v| acc + v.as_str());
-
- let own_color_str = match own_color {
- Some(v) => v,
- None => String::new(),
- };
-
- let own_out = own_formatter + own_color_str.as_str() + self.text.as_str();
-
- self.extra
- .as_ref()
- .into_iter()
- .flat_map(|v| v.into_iter())
- .map(|child| child.to_traditional_parts(&own_formatters, own_color_option))
- .fold(own_out, |acc, next| acc + next.as_str())
- }
-
- fn update_formatter(to: &mut Vec<Formatter>, formatter: Formatter, v: &Option<bool>) {
- if !to.contains(&formatter) && v.unwrap_or(false) {
- to.push(formatter)
- }
- }
-
- pub fn from_text(text: &str) -> Chat {
- Chat {
- text: text.to_owned(),
- bold: None,
- italic: None,
- underlined: None,
- strikethrough: None,
- obfuscated: None,
- color: None,
- extra: None,
- }
- }
-}
-
-impl Serialize for Chat {
- fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
- serde_json::to_string(self)
- .map_err(move |err| {
- SerializeErr::FailedJsonEncode(format!("failed to serialize chat {:?}", err))
- })?
- .mc_serialize(to)
- }
-}
-
-impl Deserialize for Chat {
- fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
- String::mc_deserialize(data)?.try_map(move |str| {
- serde_json::from_str(str.as_str()).map_err(move |err| {
- DeserializeErr::FailedJsonDeserialize(format!(
- "failed to deserialize chat {:?}",
- err
- ))
- })
- })
- }
-}
-
-#[cfg(test)]
-impl TestRandom for Chat {
- fn test_gen_random() -> Self {
- let str = String::test_gen_random();
- Chat::from_text(str.as_str())
- }
-}
-
#[derive(Default)]
pub struct BytesSerializer {
data: Vec<u8>,
diff --git a/src/uuid.rs b/src/uuid.rs
index 019fbd2..bdd9fbe 100644
--- a/src/uuid.rs
+++ b/src/uuid.rs
@@ -192,4 +192,19 @@ mod tests {
fn test_debug_uuid() {
println!("got uuid {:?}", UUID4::random());
}
+
+ #[test]
+ fn test_to_json() {
+ let id = UUID4::random();
+ let str = serde_json::to_string(&id).expect("should serialize fine");
+ assert_eq!(str, format!("\"{}\"", id.to_string()))
+ }
+
+ #[test]
+ fn test_from_json() {
+ let id = UUID4::random();
+ let json = format!("\"{}\"", id.to_string());
+ let deserialized: UUID4 = serde_json::from_str(json.as_str()).expect("should read fine");
+ assert_eq!(deserialized, id);
+ }
}
diff --git a/src/v1_15_2.rs b/src/v1_15_2.rs
index eba809d..ffc8490 100644
--- a/src/v1_15_2.rs
+++ b/src/v1_15_2.rs
@@ -3,6 +3,7 @@ use std::fmt::Debug;
#[cfg(test)]
use crate::protocol::TestRandom;
+use std::cell::Cell;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PacketDirection {
@@ -37,7 +38,7 @@ impl State {
Login => "Login",
Play => "Play",
}
- .to_owned()
+ .to_owned()
}
}
@@ -1403,7 +1404,7 @@ impl Deserialize for UpdateScoreSpec {
"invalid update score action {}",
other
))
- .into(),
+ .into(),
}?;
Deserialized::ok(
@@ -1457,7 +1458,7 @@ impl Serialize for TitleActionSpec {
Hide => 0x04,
Reset => 0x05,
})
- .mc_serialize(to)?;
+ .mc_serialize(to)?;
match self {
SetTitle(body) => to.serialize_other(body),
@@ -1916,8 +1917,8 @@ pub struct PlayerInfoAction<A: Clone + PartialEq + Debug> {
}
impl<A> Serialize for PlayerInfoAction<A>
-where
- A: Serialize + Clone + PartialEq + Debug,
+ where
+ A: Serialize + Clone + PartialEq + Debug,
{
fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
to.serialize_other(&self.uuid)?;
@@ -1926,8 +1927,8 @@ where
}
impl<A> Deserialize for PlayerInfoAction<A>
-where
- A: Deserialize + Clone + PartialEq + Debug,
+ where
+ A: Deserialize + Clone + PartialEq + Debug,
{
fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
let Deserialized { value: uuid, data } = UUID4::mc_deserialize(data)?;
@@ -1981,7 +1982,7 @@ impl PlayerInfoActionList {
UpdateDisplayName(_) => 0x03,
Remove(_) => 0x04,
}
- .into()
+ .into()
}
pub fn len(&self) -> usize {
@@ -2185,7 +2186,7 @@ impl WorldBorderAction {
SetWarningTime(_) => 0x04,
SetWarningBlocks(_) => 0x05,
}
- .into()
+ .into()
}
}
@@ -2465,7 +2466,7 @@ impl InteractKind {
Attack => 0x01,
InteractAt(_) => 0x02,
}
- .into()
+ .into()
}
}
@@ -2658,7 +2659,7 @@ impl Recipe {
CampfireCooking(_) => "minecraft:campfire_cooking",
StoneCutting(_) => "minecraft:stonecutting",
}
- .to_owned()
+ .to_owned()
}
fn serialize_body<S: Serializer>(&self, to: &mut S) -> SerializeResult {
@@ -2754,10 +2755,10 @@ impl Deserialize for RecipeSpec {
other
))),
}?
- .map(move |recipe_body| RecipeSpec {
- id: recipe_id,
- recipe: recipe_body,
- }))
+ .map(move |recipe_body| RecipeSpec {
+ id: recipe_id,
+ recipe: recipe_body,
+ }))
}
}
@@ -2917,26 +2918,11 @@ impl Serialize for ChunkData {
impl Deserialize for ChunkData {
fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
- let Deserialized {
- value: chunk_x,
- data,
- } = i32::mc_deserialize(data)?;
- let Deserialized {
- value: chunk_z,
- data,
- } = i32::mc_deserialize(data)?;
- let Deserialized {
- value: is_full_chunk,
- data,
- } = bool::mc_deserialize(data)?;
- let Deserialized {
- value: primary_bit_mask,
- data,
- } = VarInt::mc_deserialize(data)?;
- let Deserialized {
- value: heightmaps,
- mut data,
- } = NamedNbtTag::mc_deserialize(data)?;
+ let Deserialized { value: chunk_x, data } = i32::mc_deserialize(data)?;
+ let Deserialized { value: chunk_z, data } = i32::mc_deserialize(data)?;
+ let Deserialized { value: is_full_chunk, data } = bool::mc_deserialize(data)?;
+ let Deserialized { value: primary_bit_mask, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: heightmaps, mut data } = NamedNbtTag::mc_deserialize(data)?;
let biomes = if is_full_chunk {
let mut biomes: [i32; 1024] = [0i32; 1024];
for elem in &mut biomes {
@@ -2948,37 +2934,25 @@ impl Deserialize for ChunkData {
} else {
None
};
- let Deserialized {
- value: chunk_data,
- data,
- } = VarIntCountedArray::<u8>::mc_deserialize(data)?;
- let Deserialized {
- value: n_block_entities_raw,
- mut data,
- } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: chunk_data, data } = VarIntCountedArray::<u8>::mc_deserialize(data)?;
+ let Deserialized { value: n_block_entities_raw, mut data } = VarInt::mc_deserialize(data)?;
let n_block_entities = n_block_entities_raw.0 as usize;
let mut block_entities = Vec::with_capacity(n_block_entities);
for _ in 0..n_block_entities {
- let Deserialized {
- value: entity,
- data: rest,
- } = NamedNbtTag::mc_deserialize(data)?;
+ let Deserialized { value: entity, data: rest } = NamedNbtTag::mc_deserialize(data)?;
data = rest;
block_entities.push(entity);
}
- Deserialized::ok(
- ChunkData {
- chunk_x,
- chunk_z,
- primary_bit_mask,
- heightmaps,
- biomes,
- data: chunk_data,
- block_entities,
- },
- data,
- )
+ Deserialized::ok(ChunkData {
+ chunk_x,
+ chunk_z,
+ primary_bit_mask,
+ heightmaps,
+ biomes,
+ data: chunk_data,
+ block_entities,
+ }, data)
}
}
@@ -2997,204 +2971,88 @@ impl TestRandom for ChunkData {
}
}
-const LIGHT_DATA_LENGTH: usize = 2048;
-const LIGHT_DATA_SECTIONS: usize = 18;
-
-type LightingData = Vec<Option<[u8; LIGHT_DATA_LENGTH]>>;
-
-#[derive(Clone, PartialEq, Debug)]
-pub struct LightingUpdateSpec {
- pub skylight_data: LightingData,
- pub blocklight_data: LightingData,
-
- _cached_skylight_update_mask: Option<VarInt>,
- _cached_blocklight_update_mask: Option<VarInt>,
- _cached_skylight_reset_mask: Option<VarInt>,
- _cached_blocklight_reset_mask: Option<VarInt>,
-}
-
-impl Serialize for LightingUpdateSpec {
- fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
- let (
- (skylight_update_mask, skylight_reset_mask),
- (blocklight_update_mask, blocklight_reset_mask),
- ) = self.get_masks();
- to.serialize_other(&skylight_update_mask)?;
- to.serialize_other(&blocklight_update_mask)?;
- to.serialize_other(&skylight_reset_mask)?;
- to.serialize_other(&blocklight_reset_mask)?;
- Self::write_lighting_data(to, &self.skylight_data)?;
- Self::write_lighting_data(to, &self.blocklight_data)
- }
-}
-
-impl Deserialize for LightingUpdateSpec {
- fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
- let Deserialized {
- value: skylight_update_mask,
- data,
- } = VarInt::mc_deserialize(data)?;
- let Deserialized {
- value: blocklight_update_mask,
- data,
- } = VarInt::mc_deserialize(data)?;
- let Deserialized {
- value: skylight_reset_mask,
- data,
- } = VarInt::mc_deserialize(data)?;
- let Deserialized {
- value: blocklight_reset_mask,
- data,
- } = VarInt::mc_deserialize(data)?;
- let Deserialized {
- value: skylight_data,
- data,
- } = Self::read_lighting_data_arrays(&skylight_update_mask, data)?;
- let Deserialized {
- value: blocklight_data,
- data,
- } = Self::read_lighting_data_arrays(&blocklight_update_mask, data)?;
+pub const LIGHT_DATA_LENGTH: usize = 2048;
+pub const LIGHT_DATA_SECTIONS: usize = 18;
- Deserialized::ok(
- Self {
- skylight_data,
- blocklight_data,
+pub struct LightingData {
+ pub data: Vec<Option<[u8; LIGHT_DATA_LENGTH]>>,
- _cached_skylight_update_mask: Some(skylight_update_mask),
- _cached_blocklight_update_mask: Some(blocklight_update_mask),
- _cached_skylight_reset_mask: Some(skylight_reset_mask),
- _cached_blocklight_reset_mask: Some(blocklight_reset_mask),
- },
- data,
- )
- }
+ _update_mask: Cell<Option<VarInt>>,
+ _reset_mask: Cell<Option<VarInt>>,
}
-impl LightingUpdateSpec {
- pub fn compute_masks(&mut self) {
- let this = &mut *self;
- let skylight_data = &this.skylight_data;
- let blocklight_data = &this.blocklight_data;
- Self::replace_optionals_if_needed(
- (
- &mut this._cached_skylight_update_mask,
- &mut this._cached_skylight_reset_mask,
- ),
- move || Self::compute_masks_for(skylight_data),
- );
- Self::replace_optionals_if_needed(
- (
- &mut this._cached_blocklight_update_mask,
- &mut this._cached_blocklight_reset_mask,
- ),
- move || Self::compute_masks_for(blocklight_data),
- );
- }
-
- pub fn get_masks(&self) -> ((VarInt, VarInt), (VarInt, VarInt)) {
- (
- Self::read_or_compute(
- (
- &self._cached_skylight_update_mask,
- &self._cached_skylight_reset_mask,
- ),
- || Self::compute_masks_for(&self.skylight_data),
- ),
- Self::read_or_compute(
- (
- &self._cached_blocklight_update_mask,
- &self._cached_blocklight_reset_mask,
- ),
- || Self::compute_masks_for(&self.skylight_data),
- ),
- )
- }
-
- fn read_lighting_data_arrays<'a>(
- mask: &VarInt,
- mut data: &'a [u8],
- ) -> DeserializeResult<'a, LightingData> {
- let mut out = vec![None; LIGHT_DATA_SECTIONS];
+impl LightingData {
+ fn deserialize(update_mask: VarInt, reset_mask: VarInt, mut data: &[u8]) -> DeserializeResult<Self> {
+ let mut out = Vec::with_capacity(LIGHT_DATA_SECTIONS);
for i in 0..LIGHT_DATA_SECTIONS {
- if (mask.0 & (1 << i)) != 0 {
- let Deserialized {
- value: length,
- data: rest,
- } = VarInt::mc_deserialize(data)?;
- if (length.0 as usize) != LIGHT_DATA_LENGTH {
- return DeserializeErr::CannotUnderstandValue(format!(
- "all lighting update arrays are supposed to be 2048, got length of {}",
- length
- ))
- .into();
+ if update_mask.0 & (1 << i) != 0 {
+ if data.len() < LIGHT_DATA_LENGTH {
+ return Err(DeserializeErr::Eof);
}
- if rest.len() < LIGHT_DATA_LENGTH {
- return DeserializeErr::Eof.into();
- }
-
- let mut data_entry = [0u8; LIGHT_DATA_LENGTH];
- let (data_src, rest) = rest.split_at(LIGHT_DATA_LENGTH);
- data_entry.copy_from_slice(data_src);
- out[i] = Some(data_entry);
+ let (section, rest) = data.split_at(LIGHT_DATA_LENGTH);
+ let mut to_vec = [0u8; LIGHT_DATA_LENGTH];
+ to_vec.copy_from_slice(section);
+ out.push(Some(to_vec));
data = rest;
+ } else {
+ out.push(None)
}
}
- Deserialized::ok(out, data)
+ let result = Self {
+ data: out,
+ _update_mask: Cell::new(Some(update_mask)),
+ _reset_mask: Cell::new(Some(reset_mask)),
+ };
+
+ Deserialized::ok(result, data)
}
- fn compute_masks_for(data: &LightingData) -> (VarInt, VarInt) {
- let mut update_mask = 0;
- let mut reset_mask = 0;
- for i in 0..LIGHT_DATA_SECTIONS {
- match data[i] {
- Some(_) => {
- update_mask |= 1 << i;
- }
+ fn update_mask(&self) -> &VarInt {
+ Self::lazily_get(&self._update_mask, || self.compute_update_mask())
+ }
+
+ fn reset_mask(&self) -> &VarInt {
+ Self::lazily_get(&self._reset_mask, || self.compute_reset_mask())
+ }
+
+ fn lazily_get<T, F>(option: &Cell<Option<T>>, provider: F) -> &T where F: FnOnce() -> T {
+ unsafe {
+ let ptr = option.as_ptr().as_mut().expect("non-null");
+ match ptr {
+ Some(data) => data,
None => {
- reset_mask |= 1 << i;
+ ptr.replace(provider());
+ ptr.as_ref().expect("it's there")
}
}
}
+ }
- (VarInt(update_mask), VarInt(reset_mask))
+ fn compute_update_mask(&self) -> VarInt {
+ self.compute_has_mask(true)
}
- fn replace_optionals_if_needed<T1, T2, F>(
- targets: (&mut Option<T1>, &mut Option<T2>),
- update: F,
- ) where
- F: FnOnce() -> (T1, T2),
- {
- let (a, b) = targets;
- if a.is_none() || b.is_none() {
- let (v_a, v_b) = update();
- *a = Some(v_a);
- *b = Some(v_b);
- }
+ fn compute_reset_mask(&self) -> VarInt {
+ self.compute_has_mask(false)
}
- fn read_or_compute<T1, T2, F>(targets: (&Option<T1>, &Option<T2>), update: F) -> (T1, T2)
- where
- F: FnOnce() -> (T1, T2),
- T1: Copy,
- T2: Copy,
- {
- let (a, b) = targets;
- if a.is_none() || b.is_none() {
- let (v_a, v_b) = update();
- (v_a, v_b)
- } else {
- (a.unwrap(), b.unwrap())
+ fn compute_has_mask(&self, has: bool) -> VarInt {
+ let mut out: u32 = 0;
+ for i in 0..LIGHT_DATA_SECTIONS {
+ if self.data[i].is_some() == has {
+ out |= 1 << i;
+ }
}
+
+ VarInt(out as i32)
}
- fn write_lighting_data<S: Serializer>(to: &mut S, data: &LightingData) -> SerializeResult {
- for i in 0..LIGHT_DATA_SECTIONS {
- if let Some(entry) = &data[i] {
- to.serialize_other(&VarInt(LIGHT_DATA_LENGTH as i32))?;
- to.serialize_bytes(&entry[..])?;
+ fn serialize_data<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ for item in &self.data {
+ if let Some(contents) = item {
+ to.serialize_bytes(&contents[..])?;
}
}
@@ -3202,36 +3060,57 @@ impl LightingUpdateSpec {
}
}
-#[cfg(test)]
-impl TestRandom for LightingUpdateSpec {
- fn test_gen_random() -> Self {
- let (skylight_update_mask, skylight_reset_mask, skylight_data) =
- Self::gen_random_lighting_data();
- let (blocklight_update_mask, blocklight_reset_mask, blocklight_data) =
- Self::gen_random_lighting_data();
+impl std::fmt::Debug for LightingData {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "LightingData(update={:018b}, reset={:018b}, size={}, bytes={})",
+ self.update_mask().0,
+ self.reset_mask().0,
+ self.data.iter().filter(move |v| v.is_some()).count(),
+ self.data.iter()
+ .filter_map(move |v| v.
+ map(move |arr| arr.len()))
+ .sum::<usize>())
+ }
+}
- Self {
- skylight_data,
- blocklight_data,
+impl std::fmt::Display for LightingData {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ <dyn std::fmt::Debug>::fmt(self, f)
+ }
+}
- _cached_skylight_update_mask: Some(skylight_update_mask),
- _cached_blocklight_update_mask: Some(blocklight_update_mask),
- _cached_skylight_reset_mask: Some(skylight_reset_mask),
- _cached_blocklight_reset_mask: Some(blocklight_reset_mask),
+impl Clone for LightingData {
+ fn clone(&self) -> Self {
+ Self {
+ data: self.data.clone(),
+ _update_mask: Cell::new(Some(self.update_mask().clone())),
+ _reset_mask: Cell::new(Some(self.reset_mask().clone())),
}
}
}
+impl PartialEq for LightingData {
+ fn eq(&self, other: &Self) -> bool {
+ self.data.eq(&other.data) &&
+ self.update_mask().eq(other.update_mask()) &&
+ self.reset_mask().eq(other.reset_mask())
+ }
+}
+
#[cfg(test)]
-impl LightingUpdateSpec {
+impl LightingData {
fn gen_random_mask() -> i32 {
let rand: u32 = rand::random();
(rand & ((1 << 19) - 1)) as i32
}
+}
- fn gen_random_lighting_data() -> (VarInt, VarInt, LightingData) {
+#[cfg(test)]
+impl TestRandom for LightingData {
+ fn test_gen_random() -> Self {
let set_mask = Self::gen_random_mask();
- let reset_mask = !set_mask & ((1 << 19) - 1);
let mut data = vec![None; LIGHT_DATA_SECTIONS];
for i in 0..LIGHT_DATA_SECTIONS {
if (set_mask & (1 << i)) != 0 {
@@ -3243,7 +3122,65 @@ impl LightingUpdateSpec {
}
}
- (VarInt(set_mask), VarInt(reset_mask), data)
+ let cache_masks = rand::random::<bool>();
+
+ if cache_masks {
+ Self {
+ data,
+ _update_mask: Cell::new(Some(VarInt(set_mask))),
+ _reset_mask: Cell::new(None),
+ }
+ } else {
+ Self {
+ data,
+ _update_mask: Cell::new(None),
+ _reset_mask: Cell::new(None),
+ }
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct LightingUpdateSpec {
+ pub skylight_data: LightingData,
+ pub blocklight_data: LightingData,
+}
+
+impl Serialize for LightingUpdateSpec {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ self.skylight_data.update_mask().mc_serialize(to)?;
+ self.blocklight_data.update_mask().mc_serialize(to)?;
+ self.skylight_data.reset_mask().mc_serialize(to)?;
+ self.blocklight_data.reset_mask().mc_serialize(to)?;
+ self.skylight_data.serialize_data(to)?;
+ self.blocklight_data.serialize_data(to)
+ }
+}
+
+impl Deserialize for LightingUpdateSpec {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: skylight_update_mask, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: blocklight_update_mask, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: skylight_reset_mask, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: blocklight_reset_mask, data } = VarInt::mc_deserialize(data)?;
+
+ let Deserialized { value: skylight_data, data } = LightingData::deserialize(skylight_update_mask, skylight_reset_mask, data)?;
+ let Deserialized { value: blocklight_data, data } = LightingData::deserialize(blocklight_update_mask, blocklight_reset_mask, data)?;
+
+ Deserialized::ok(Self {
+ skylight_data,
+ blocklight_data,
+ }, data)
+ }
+}
+
+#[cfg(test)]
+impl TestRandom for LightingUpdateSpec {
+ fn test_gen_random() -> Self {
+ Self {
+ skylight_data: LightingData::test_gen_random(),
+ blocklight_data: LightingData::test_gen_random(),
+ }
}
}