mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
293 lines
13 KiB
Rust
Executable file
293 lines
13 KiB
Rust
Executable file
use serde::{de, Deserialize, Deserializer};
|
|
|
|
use crate::{
|
|
base_component::BaseComponent,
|
|
style::{ChatFormatting, Style},
|
|
text_component::TextComponent,
|
|
translatable_component::{StringOrComponent, TranslatableComponent},
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Component {
|
|
Text(TextComponent),
|
|
Translatable(TranslatableComponent),
|
|
}
|
|
|
|
lazy_static! {
|
|
pub static ref DEFAULT_STYLE: Style = Style {
|
|
color: Some(ChatFormatting::WHITE.try_into().unwrap()),
|
|
..Style::default()
|
|
};
|
|
}
|
|
|
|
/// A chat component
|
|
impl Component {
|
|
pub fn get_base_mut(&mut self) -> &mut BaseComponent {
|
|
match self {
|
|
Self::Text(c) => &mut c.base,
|
|
Self::Translatable(c) => &mut c.base,
|
|
}
|
|
}
|
|
|
|
pub fn get_base(&self) -> &BaseComponent {
|
|
match self {
|
|
Self::Text(c) => &c.base,
|
|
Self::Translatable(c) => &c.base,
|
|
}
|
|
}
|
|
|
|
/// Add a component as a sibling of this one
|
|
fn append(&mut self, sibling: Component) {
|
|
self.get_base_mut().siblings.push(sibling);
|
|
}
|
|
|
|
/// Get the "separator" component from the json
|
|
fn parse_separator(json: &serde_json::Value) -> Result<Option<Component>, serde_json::Error> {
|
|
if json.get("separator").is_some() {
|
|
return Ok(Some(Component::deserialize(
|
|
json.get("separator").unwrap(),
|
|
)?));
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
/// Convert this component into an ansi string
|
|
pub fn to_ansi(&self, default_style: Option<&Style>) -> String {
|
|
// default the default_style to white if it's not set
|
|
let default_style: &Style = default_style.unwrap_or(&DEFAULT_STYLE);
|
|
|
|
// 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
|
|
let mut running_style = Style::default();
|
|
|
|
for component in self.clone().into_iter() {
|
|
let component_text = match &component {
|
|
Self::Text(c) => c.text.to_string(),
|
|
Self::Translatable(c) => c.to_string(),
|
|
};
|
|
|
|
let component_style = &component.get_base().style;
|
|
|
|
let ansi_text = running_style.compare_ansi(component_style, default_style);
|
|
built_string.push_str(&ansi_text);
|
|
built_string.push_str(&component_text);
|
|
|
|
running_style.apply(component_style);
|
|
}
|
|
|
|
if !running_style.is_empty() {
|
|
built_string.push_str("\u{1b}[m");
|
|
}
|
|
|
|
built_string
|
|
}
|
|
}
|
|
|
|
impl IntoIterator for Component {
|
|
/// Recursively call the function for every component in this component
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
let base = self.get_base();
|
|
let siblings = base.siblings.clone();
|
|
let mut v: Vec<Component> = Vec::with_capacity(siblings.len() + 1);
|
|
v.push(self);
|
|
for sibling in siblings {
|
|
v.extend(sibling.into_iter());
|
|
}
|
|
|
|
v.into_iter()
|
|
}
|
|
|
|
type Item = Component;
|
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Component {
|
|
fn deserialize<D>(de: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let json: serde_json::Value = serde::Deserialize::deserialize(de)?;
|
|
|
|
// we create a component that we might add siblings to
|
|
let mut component: Component;
|
|
|
|
// if it's primitive, make it a text component
|
|
if !json.is_array() && !json.is_object() {
|
|
return Ok(Component::Text(TextComponent::new(
|
|
json.as_str().unwrap_or("").to_string(),
|
|
)));
|
|
}
|
|
// if it's an object, do things with { text } and stuff
|
|
else if json.is_object() {
|
|
if json.get("text").is_some() {
|
|
let text = json.get("text").unwrap().as_str().unwrap_or("").to_string();
|
|
component = Component::Text(TextComponent::new(text));
|
|
} else if json.get("translate").is_some() {
|
|
let translate = json
|
|
.get("translate")
|
|
.unwrap()
|
|
.as_str()
|
|
.ok_or_else(|| de::Error::custom("\"translate\" must be a string"))?
|
|
.into();
|
|
if json.get("with").is_some() {
|
|
let with = json.get("with").unwrap().as_array().unwrap();
|
|
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 = Component::deserialize(item).map_err(de::Error::custom)?;
|
|
if let Component::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::Component(
|
|
Component::deserialize(item).map_err(de::Error::custom)?,
|
|
));
|
|
}
|
|
component =
|
|
Component::Translatable(TranslatableComponent::new(translate, with_array));
|
|
} else {
|
|
// if it doesn't have a "with", just have the with_array be empty
|
|
component =
|
|
Component::Translatable(TranslatableComponent::new(translate, Vec::new()));
|
|
}
|
|
} else if json.get("score").is_some() {
|
|
// object = GsonHelper.getAsJsonObject(jsonObject, "score");
|
|
let score_json = json.get("score").unwrap();
|
|
// if (!object.has("name") || !object.has("objective")) throw new JsonParseException("A score component needs a least a name and an objective");
|
|
// ScoreComponent scoreComponent = new ScoreComponent(GsonHelper.getAsString((JsonObject)object, "name"), GsonHelper.getAsString((JsonObject)object, "objective"));
|
|
if score_json.get("name").is_none() || score_json.get("objective").is_none() {
|
|
return Err(de::Error::missing_field(
|
|
"A score component needs at least a name and an objective",
|
|
));
|
|
}
|
|
// TODO
|
|
return Err(de::Error::custom(
|
|
"score text components aren't yet supported",
|
|
));
|
|
// component = ScoreComponent
|
|
} else if json.get("selector").is_some() {
|
|
// } else if (jsonObject.has("selector")) {
|
|
// object = this.parseSeparator(type, jsonDeserializationContext, jsonObject);
|
|
// SelectorComponent selectorComponent = new SelectorComponent(GsonHelper.getAsString(jsonObject, "selector"), (Optional<Component>)object);
|
|
|
|
return Err(de::Error::custom(
|
|
"selector text components aren't yet supported",
|
|
));
|
|
// } else if (jsonObject.has("keybind")) {
|
|
// KeybindComponent keybindComponent = new KeybindComponent(GsonHelper.getAsString(jsonObject, "keybind"));
|
|
} else if json.get("keybind").is_some() {
|
|
return Err(de::Error::custom(
|
|
"keybind text components aren't yet supported",
|
|
));
|
|
} else {
|
|
// } else {
|
|
// if (!jsonObject.has("nbt")) throw new JsonParseException("Don't know how to turn " + jsonElement + " into a Component");
|
|
if json.get("nbt").is_none() {
|
|
return Err(de::Error::custom(
|
|
format!("Don't know how to turn {} into a Component", json).as_str(),
|
|
));
|
|
}
|
|
// object = GsonHelper.getAsString(jsonObject, "nbt");
|
|
let _nbt = json.get("nbt").unwrap().to_string();
|
|
// Optional<Component> optional = this.parseSeparator(type, jsonDeserializationContext, jsonObject);
|
|
let _separator = Component::parse_separator(&json).map_err(de::Error::custom)?;
|
|
|
|
let _interpret = match json.get("interpret") {
|
|
Some(v) => v.as_bool().ok_or(Some(false)).unwrap(),
|
|
None => false,
|
|
};
|
|
// boolean bl = GsonHelper.getAsBoolean(jsonObject, "interpret", false);
|
|
// if (jsonObject.has("block")) {
|
|
if json.get("block").is_some() {}
|
|
return Err(de::Error::custom(
|
|
"nbt text components aren't yet supported",
|
|
));
|
|
// NbtComponent.BlockNbtComponent blockNbtComponent = new NbtComponent.BlockNbtComponent((String)object, bl, GsonHelper.getAsString(jsonObject, "block"), optional);
|
|
// } else if (jsonObject.has("entity")) {
|
|
// NbtComponent.EntityNbtComponent entityNbtComponent = new NbtComponent.EntityNbtComponent((String)object, bl, GsonHelper.getAsString(jsonObject, "entity"), optional);
|
|
// } else {
|
|
// if (!jsonObject.has("storage")) throw new JsonParseException("Don't know how to turn " + jsonElement + " into a Component");
|
|
// NbtComponent.StorageNbtComponent storageNbtComponent = new NbtComponent.StorageNbtComponent((String)object, bl, new ResourceLocation(GsonHelper.getAsString(jsonObject, "storage")), optional);
|
|
// }
|
|
// }
|
|
}
|
|
// if (jsonObject.has("extra")) {
|
|
// object = GsonHelper.getAsJsonArray(jsonObject, "extra");
|
|
// if (object.size() <= 0) throw new JsonParseException("Unexpected empty array of components");
|
|
// for (int i = 0; i < object.size(); ++i) {
|
|
// var5_17.append(this.deserialize(object.get(i), type, jsonDeserializationContext));
|
|
// }
|
|
// }
|
|
// var5_17.setStyle((Style)jsonDeserializationContext.deserialize(jsonElement, Style.class));
|
|
// return var5_17;
|
|
// }
|
|
if json.get("extra").is_some() {
|
|
let extra = match json.get("extra").unwrap().as_array() {
|
|
Some(r) => r,
|
|
None => return Err(de::Error::custom("Extra isn't an array")),
|
|
};
|
|
if extra.is_empty() {
|
|
return Err(de::Error::custom("Unexpected empty array of components"));
|
|
}
|
|
for extra_component in extra {
|
|
let sibling =
|
|
Component::deserialize(extra_component).map_err(de::Error::custom)?;
|
|
component.append(sibling);
|
|
}
|
|
}
|
|
|
|
let style = Style::deserialize(&json);
|
|
component.get_base_mut().style = style;
|
|
|
|
return Ok(component);
|
|
}
|
|
// ok so it's not an object, if it's an array deserialize every item
|
|
else if !json.is_array() {
|
|
return Err(de::Error::custom(
|
|
format!("Don't know how to turn {} into a Component", json).as_str(),
|
|
));
|
|
}
|
|
let json_array = json.as_array().unwrap();
|
|
// the first item in the array is the one that we're gonna return, the others are siblings
|
|
let mut component = Component::deserialize(&json_array[0]).map_err(de::Error::custom)?;
|
|
for i in 1..json_array.len() {
|
|
component.append(
|
|
Component::deserialize(json_array.get(i).unwrap()).map_err(de::Error::custom)?,
|
|
);
|
|
}
|
|
Ok(component)
|
|
}
|
|
}
|
|
|
|
impl McBufReadable for Component {
|
|
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
|
let string = buf.read_utf()?;
|
|
let json: serde_json::Value = serde_json::from_str(string.as_str())
|
|
.map_err(|_| "Component isn't valid JSON".to_string())?;
|
|
let component = Component::deserialize(json).map_err(|e| e.to_string())?;
|
|
Ok(component)
|
|
}
|
|
}
|
|
|
|
impl McBufWritable for Component {
|
|
// async fn read_into(buf: &mut impl Read) -> Result<Self, String>
|
|
// where
|
|
// R: AsyncRead + std::marker::Unpin + std::marker::Send,
|
|
// {
|
|
// let string = buf.read_utf().await?;
|
|
// let json: serde_json::Value = serde_json::from_str(string.as_str())
|
|
// .map_err(|e| "Component isn't valid JSON".to_string())?;
|
|
// let component = Component::deserialize(json).map_err(|e| e.to_string())?;
|
|
// Ok(component)
|
|
// }
|
|
fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
|
|
// component doesn't have serialize implemented yet
|
|
todo!()
|
|
}
|
|
}
|