mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
Serialize Component (#47)
* Serializing ClientboundStatusResponsePacket Enable serialization of ClientboundStatusResponsePacket * Update clientbound_status_response_packet.rs Add options previewsChat and enforcesSecureChat * Serialize Style and TextColor * Serialize BaseComponent * Serialize TextComponent * Fix Style * Serialize Component * Fix multiple formats per message, fix reset tag * Fix Style, again * Use FlatMapSerializer * Forgot italics * Count struct fields, reorganize logic * Serialize TranslatableComponent * Rewrite TextComponent Serializer * Fix using TextColor::Parse * Code Cleanup * Add default attribute, just in case * Clippy * use serde derive feature + preferred formatting choices Co-authored-by: BuildTools <unconfigured@null.spigotmc.org> Co-authored-by: mat <github@matdoes.dev>
This commit is contained in:
parent
661c3622be
commit
3b93fc6412
8 changed files with 143 additions and 32 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -1690,9 +1690,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.147"
|
||||
version = "1.0.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
|
||||
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -1709,9 +1709,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.147"
|
||||
version = "1.0.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
|
||||
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1800,9 +1800,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.103"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
|
||||
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -3,14 +3,14 @@ description = "Parse Minecraft chat messages."
|
|||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "azalea-chat"
|
||||
version = "0.4.0"
|
||||
repository = "https://github.com/mat-1/azalea/tree/main/azalea-chat"
|
||||
version = "0.4.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
azalea-buf = {path = "../azalea-buf", features = ["serde_json"], version = "^0.4.0" }
|
||||
azalea-language = {path = "../azalea-language", version = "^0.4.0" }
|
||||
azalea-buf = {path = "../azalea-buf", features = ["serde_json"], version = "^0.4.0"}
|
||||
azalea-language = {path = "../azalea-language", version = "^0.4.0"}
|
||||
once_cell = "1.16.0"
|
||||
serde = "^1.0.130"
|
||||
serde = {version = "^1.0.148", features = ["derive"]}
|
||||
serde_json = "^1.0.72"
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use crate::{style::Style, Component};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct BaseComponent {
|
||||
// implements mutablecomponent
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub siblings: Vec<Component>,
|
||||
#[serde(flatten)]
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
|
|
|
@ -6,14 +6,15 @@ use crate::{
|
|||
};
|
||||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{Cursor, Write},
|
||||
};
|
||||
|
||||
/// A chat component, basically anything you can see in chat.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Component {
|
||||
Text(TextComponent),
|
||||
Translatable(TranslatableComponent),
|
||||
|
@ -262,11 +263,10 @@ impl McBufReadable for Component {
|
|||
}
|
||||
|
||||
impl McBufWritable for Component {
|
||||
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(())
|
||||
todo!()
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::{collections::HashMap, fmt};
|
|||
|
||||
use azalea_buf::McBuf;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{ser::SerializeStruct, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -10,6 +11,19 @@ pub struct TextColor {
|
|||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
impl Serialize for TextColor {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextColor {
|
||||
pub fn parse(value: String) -> Option<TextColor> {
|
||||
if value.starts_with('#') {
|
||||
|
@ -276,17 +290,67 @@ impl TryFrom<ChatFormatting> for TextColor {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Style {
|
||||
// these are options instead of just bools because None is different than false in this case
|
||||
// These are options instead of just bools because None is different than false in this case
|
||||
pub color: Option<TextColor>,
|
||||
pub bold: Option<bool>,
|
||||
pub italic: Option<bool>,
|
||||
pub underlined: Option<bool>,
|
||||
pub strikethrough: Option<bool>,
|
||||
pub obfuscated: Option<bool>,
|
||||
/// Whether it should reset the formatting before applying these styles
|
||||
/// Whether formatting should be reset before applying these styles
|
||||
pub reset: bool,
|
||||
}
|
||||
|
||||
impl Serialize for Style {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let len = if self.reset {
|
||||
6
|
||||
} else {
|
||||
usize::from(self.color.is_some())
|
||||
+ usize::from(self.bold.is_some())
|
||||
+ usize::from(self.italic.is_some())
|
||||
+ usize::from(self.underlined.is_some())
|
||||
+ usize::from(self.strikethrough.is_some())
|
||||
+ 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)?;
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn empty() -> Self {
|
||||
Self::default()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use crate::{base_component::BaseComponent, style::ChatFormatting, Component};
|
||||
use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
|
||||
use std::fmt::Display;
|
||||
|
||||
/// A component that contains text that's the same in all locales.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
@ -9,6 +9,21 @@ pub struct TextComponent {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
impl Serialize for TextComponent {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_map(None)?;
|
||||
state.serialize_entry("text", &self.text)?;
|
||||
Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?;
|
||||
if !self.base.siblings.is_empty() {
|
||||
state.serialize_entry("extra", &self.base.siblings)?;
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
const LEGACY_FORMATTING_CODE_SYMBOL: char = '§';
|
||||
|
||||
/// Convert a legacy color code string into a Component
|
||||
|
|
|
@ -3,8 +3,10 @@ use std::fmt::{self, Display, Formatter};
|
|||
use crate::{
|
||||
base_component::BaseComponent, style::Style, text_component::TextComponent, Component,
|
||||
};
|
||||
use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum StringOrComponent {
|
||||
String(String),
|
||||
Component(Component),
|
||||
|
@ -18,6 +20,19 @@ pub struct TranslatableComponent {
|
|||
pub args: Vec<StringOrComponent>,
|
||||
}
|
||||
|
||||
impl Serialize for TranslatableComponent {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_map(None)?;
|
||||
state.serialize_entry("translate", &self.key)?;
|
||||
Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?;
|
||||
state.serialize_entry("with", &self.args)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl TranslatableComponent {
|
||||
pub fn new(key: String, args: Vec<StringOrComponent>) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||
use azalea_chat::Component;
|
||||
use azalea_protocol_macros::ClientboundStatusPacket;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{value::Serializer, Value};
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Version {
|
||||
pub name: Component,
|
||||
pub name: String,
|
||||
pub protocol: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SamplePlayer {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Players {
|
||||
pub max: i32,
|
||||
pub online: i32,
|
||||
|
@ -26,12 +26,22 @@ pub struct Players {
|
|||
}
|
||||
|
||||
// the entire packet is just json, which is why it has deserialize
|
||||
#[derive(Clone, Debug, Deserialize, ClientboundStatusPacket)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ClientboundStatusPacket)]
|
||||
pub struct ClientboundStatusResponsePacket {
|
||||
pub description: Component,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub favicon: Option<String>,
|
||||
pub players: Players,
|
||||
pub version: Version,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "previewsChat")]
|
||||
pub previews_chat: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "enforcesSecureChat")]
|
||||
pub enforces_secure_chat: Option<bool>,
|
||||
}
|
||||
|
||||
impl McBufReadable for ClientboundStatusResponsePacket {
|
||||
|
@ -44,7 +54,11 @@ impl McBufReadable for ClientboundStatusResponsePacket {
|
|||
}
|
||||
|
||||
impl McBufWritable for ClientboundStatusResponsePacket {
|
||||
fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
todo!()
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
let status_string = ClientboundStatusResponsePacket::serialize(self, Serializer)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
status_string.write_into(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue