mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
correct minecraft-chat 🎉
This commit is contained in:
parent
6026c74430
commit
ba911a8a20
14 changed files with 395 additions and 302 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -289,6 +289,7 @@ name = "minecraft-chat"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -308,6 +309,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"minecraft-chat",
|
"minecraft-chat",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -505,6 +507,20 @@ name = "serde"
|
||||||
version = "1.0.130"
|
version = "1.0.130"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.130"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use minecraft_client;
|
|
||||||
use minecraft_protocol::ServerAddress;
|
use minecraft_protocol::ServerAddress;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,5 @@ version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
serde = "^1.0.130"
|
||||||
serde_json = "^1.0.72"
|
serde_json = "^1.0.72"
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use serde_json;
|
use serde::{
|
||||||
|
de::{self, Error},
|
||||||
|
Deserialize, Deserializer,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base_component::BaseComponent,
|
base_component::BaseComponent,
|
||||||
style::Style,
|
style::{ChatFormatting, Style},
|
||||||
text_component::TextComponent,
|
text_component::TextComponent,
|
||||||
translatable_component::{StringOrComponent, TranslatableComponent},
|
translatable_component::{StringOrComponent, TranslatableComponent},
|
||||||
};
|
};
|
||||||
|
@ -13,141 +16,15 @@ pub enum Component {
|
||||||
Translatable(TranslatableComponent),
|
Translatable(TranslatableComponent),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref DEFAULT_STYLE: Style = Style {
|
||||||
|
color: Some(ChatFormatting::WHITE.try_into().unwrap()),
|
||||||
|
..Style::default()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// A chat component
|
/// A chat component
|
||||||
impl Component {
|
impl Component {
|
||||||
pub fn new(json: &serde_json::Value) -> Result<Component, String> {
|
|
||||||
// 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().to_string();
|
|
||||||
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 i in 0..with.len() {
|
|
||||||
// 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::new(&with[i])?;
|
|
||||||
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));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with_array.push(StringOrComponent::Component(Component::new(&with[i])?));
|
|
||||||
}
|
|
||||||
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(
|
|
||||||
"A score component needs at least a name and an objective".to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
return Err("score text components aren't yet supported".to_string());
|
|
||||||
// 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("selector text components aren't yet supported".to_string());
|
|
||||||
// } else if (jsonObject.has("keybind")) {
|
|
||||||
// KeybindComponent keybindComponent = new KeybindComponent(GsonHelper.getAsString(jsonObject, "keybind"));
|
|
||||||
} else if json.get("keybind").is_some() {
|
|
||||||
return Err("keybind text components aren't yet supported".to_string());
|
|
||||||
} 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(format!("Don't know how to turn {} into a Component", json));
|
|
||||||
}
|
|
||||||
// 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)?;
|
|
||||||
|
|
||||||
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("nbt text components aren't yet supported".to_string());
|
|
||||||
// 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("Extra isn't an array".to_string()),
|
|
||||||
};
|
|
||||||
if extra.is_empty() {
|
|
||||||
return Err("Unexpected empty array of components".to_string());
|
|
||||||
}
|
|
||||||
for extra_component in extra {
|
|
||||||
component.append(Component::new(extra_component)?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(format!("Don't know how to turn {} into a Component", json));
|
|
||||||
}
|
|
||||||
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::new(&json_array[0])?;
|
|
||||||
for i in 1..json_array.len() {
|
|
||||||
component.append(Component::new(json_array.get(i).unwrap())?);
|
|
||||||
}
|
|
||||||
Ok(component)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is it possible to use a macro so this doesn't have to be duplicated?
|
// TODO: is it possible to use a macro so this doesn't have to be duplicated?
|
||||||
|
|
||||||
pub fn get_base_mut(&mut self) -> &mut BaseComponent {
|
pub fn get_base_mut(&mut self) -> &mut BaseComponent {
|
||||||
|
@ -170,15 +47,20 @@ impl Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the "separator" component from the json
|
/// Get the "separator" component from the json
|
||||||
fn parse_separator(json: &serde_json::Value) -> Result<Option<Component>, String> {
|
fn parse_separator(json: &serde_json::Value) -> Result<Option<Component>, serde_json::Error> {
|
||||||
if json.get("separator").is_some() {
|
if json.get("separator").is_some() {
|
||||||
return Ok(Some(Component::new(json.get("separator").unwrap())?));
|
return Ok(Some(Component::deserialize(
|
||||||
|
json.get("separator").unwrap(),
|
||||||
|
)?));
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this component into an ansi string
|
/// Convert this component into an ansi string
|
||||||
pub fn to_ansi(&self) -> 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_else(|| &DEFAULT_STYLE);
|
||||||
|
|
||||||
// this contains the final string will all the ansi escape codes
|
// this contains the final string will all the ansi escape codes
|
||||||
let mut built_string = String::new();
|
let mut built_string = String::new();
|
||||||
// this style will update as we visit components
|
// this style will update as we visit components
|
||||||
|
@ -191,7 +73,7 @@ impl Component {
|
||||||
};
|
};
|
||||||
let component_style = &component.get_base().style;
|
let component_style = &component.get_base().style;
|
||||||
|
|
||||||
let ansi_text = running_style.compare_ansi(component_style);
|
let ansi_text = running_style.compare_ansi(component_style, default_style);
|
||||||
built_string.push_str(&ansi_text);
|
built_string.push_str(&ansi_text);
|
||||||
built_string.push_str(component_text);
|
built_string.push_str(component_text);
|
||||||
|
|
||||||
|
@ -223,3 +105,163 @@ impl IntoIterator for Component {
|
||||||
type Item = Component;
|
type Item = Component;
|
||||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
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>,
|
||||||
|
{
|
||||||
|
println!("deserializing component");
|
||||||
|
let json: serde_json::Value = serde::Deserialize::deserialize(de)?;
|
||||||
|
println!("made json");
|
||||||
|
|
||||||
|
// 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().to_string();
|
||||||
|
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 i in 0..with.len() {
|
||||||
|
// 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(&with[i]).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));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with_array.push(StringOrComponent::Component(
|
||||||
|
Component::deserialize(&with[i]).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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -188,29 +188,21 @@ impl TextColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from ChatFormatting to TextColor
|
||||||
|
impl TryFrom<ChatFormatting<'_>> for TextColor {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(formatter: ChatFormatting<'_>) -> Result<Self, Self::Error> {
|
||||||
|
if formatter.is_format {
|
||||||
|
return Err(format!("{} is not a color", formatter.name));
|
||||||
|
}
|
||||||
|
let color = formatter.color.unwrap_or(0);
|
||||||
|
Ok(Self::new(color, Some(formatter.name.to_string())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
// @Nullable
|
|
||||||
// final TextColor color;
|
|
||||||
// @Nullable
|
|
||||||
// final Boolean bold;
|
|
||||||
// @Nullable
|
|
||||||
// final Boolean italic;
|
|
||||||
// @Nullable
|
|
||||||
// final Boolean underlined;
|
|
||||||
// @Nullable
|
|
||||||
// final Boolean strikethrough;
|
|
||||||
// @Nullable
|
|
||||||
// final Boolean obfuscated;
|
|
||||||
// @Nullable
|
|
||||||
// final ClickEvent clickEvent;
|
|
||||||
// @Nullable
|
|
||||||
// final HoverEvent hoverEvent;
|
|
||||||
// @Nullable
|
|
||||||
// final String insertion;
|
|
||||||
// @Nullable
|
|
||||||
// final ResourceLocation font;
|
|
||||||
|
|
||||||
// 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 color: Option<TextColor>,
|
||||||
pub bold: Option<bool>,
|
pub bold: Option<bool>,
|
||||||
|
@ -218,17 +210,24 @@ pub struct Style {
|
||||||
pub underlined: Option<bool>,
|
pub underlined: Option<bool>,
|
||||||
pub strikethrough: Option<bool>,
|
pub strikethrough: Option<bool>,
|
||||||
pub obfuscated: Option<bool>,
|
pub obfuscated: Option<bool>,
|
||||||
|
/// Whether it should reset the formatting before applying these styles
|
||||||
|
pub reset: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style {
|
impl Style {
|
||||||
pub fn default() -> Style {
|
pub fn default() -> Self {
|
||||||
Style {
|
Self::empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
color: None,
|
color: None,
|
||||||
bold: None,
|
bold: None,
|
||||||
italic: None,
|
italic: None,
|
||||||
underlined: None,
|
underlined: None,
|
||||||
strikethrough: None,
|
strikethrough: None,
|
||||||
obfuscated: None,
|
obfuscated: None,
|
||||||
|
reset: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,6 +250,7 @@ impl Style {
|
||||||
underlined,
|
underlined,
|
||||||
strikethrough,
|
strikethrough,
|
||||||
obfuscated,
|
obfuscated,
|
||||||
|
..Style::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Style::default()
|
Style::default()
|
||||||
|
@ -268,42 +268,35 @@ impl Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// find the necessary ansi code to get from this style to another
|
/// find the necessary ansi code to get from this style to another
|
||||||
pub fn compare_ansi(&self, after: &Style) -> String {
|
pub fn compare_ansi(&self, after: &Style, default_style: &Style) -> String {
|
||||||
let should_reset = {
|
let should_reset = after.reset ||
|
||||||
// if it used to be bold and now it's not, reset
|
// if it used to be bold and now it's not, reset
|
||||||
if self.bold.unwrap_or(false) && !after.bold.unwrap_or(true) {
|
(self.bold.unwrap_or(false) && !after.bold.unwrap_or(true)) ||
|
||||||
true
|
|
||||||
}
|
|
||||||
// if it used to be italic and now it's not, reset
|
// if it used to be italic and now it's not, reset
|
||||||
else if self.italic.unwrap_or(false) && !after.italic.unwrap_or(true) {
|
(self.italic.unwrap_or(false) && !after.italic.unwrap_or(true)) ||
|
||||||
true
|
|
||||||
// if it used to be underlined and now it's not, reset
|
// if it used to be underlined and now it's not, reset
|
||||||
} else if self.underlined.unwrap_or(false) && !after.underlined.unwrap_or(true) {
|
(self.underlined.unwrap_or(false) && !after.underlined.unwrap_or(true)) ||
|
||||||
true
|
|
||||||
// if it used to be strikethrough and now it's not, reset
|
// if it used to be strikethrough and now it's not, reset
|
||||||
} else if self.strikethrough.unwrap_or(false) && !after.strikethrough.unwrap_or(true) {
|
(self.strikethrough.unwrap_or(false) && !after.strikethrough.unwrap_or(true)) ||
|
||||||
true
|
|
||||||
// if it used to be obfuscated and now it's not, reset
|
// if it used to be obfuscated and now it's not, reset
|
||||||
} else {
|
(self.obfuscated.unwrap_or(false) && !after.obfuscated.unwrap_or(true));
|
||||||
self.obfuscated.unwrap_or(false) && !after.obfuscated.unwrap_or(true)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ansi_codes = String::new();
|
let mut ansi_codes = String::new();
|
||||||
|
|
||||||
let (before, after) = if should_reset {
|
let empty_style = Style::empty();
|
||||||
// if it's true before and none after, make it true after
|
|
||||||
// if it's false before and none after, make it false after
|
|
||||||
// we should apply after into before and use that as after
|
|
||||||
ansi_codes.push_str(Ansi::RESET);
|
|
||||||
let mut updated_after = self.clone();
|
|
||||||
updated_after.apply(after);
|
|
||||||
(Style::default(), updated_after)
|
|
||||||
} else {
|
|
||||||
(self.clone(), after.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("should_reset {:?}", should_reset);
|
let (before, after) = if should_reset {
|
||||||
|
ansi_codes.push_str(Ansi::RESET);
|
||||||
|
let mut updated_after = if after.reset {
|
||||||
|
default_style.clone()
|
||||||
|
} else {
|
||||||
|
self.clone()
|
||||||
|
};
|
||||||
|
updated_after.apply(after);
|
||||||
|
(&empty_style, updated_after)
|
||||||
|
} else {
|
||||||
|
(self, after.clone())
|
||||||
|
};
|
||||||
|
|
||||||
// if bold used to be false/default and now it's true, set bold
|
// if bold used to be false/default and now it's true, set bold
|
||||||
if !before.bold.unwrap_or(false) && after.bold.unwrap_or(false) {
|
if !before.bold.unwrap_or(false) && after.bold.unwrap_or(false) {
|
||||||
|
@ -331,7 +324,7 @@ impl Style {
|
||||||
if before.color.is_none() && after.color.is_some() {
|
if before.color.is_none() && after.color.is_some() {
|
||||||
true
|
true
|
||||||
} else if before.color.is_some() && after.color.is_some() {
|
} else if before.color.is_some() && after.color.is_some() {
|
||||||
before.color.unwrap().value != after.color.as_ref().unwrap().value
|
before.color.clone().unwrap().value != after.color.as_ref().unwrap().value
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -369,21 +362,14 @@ impl Style {
|
||||||
|
|
||||||
/// Apply a ChatFormatting to this style
|
/// Apply a ChatFormatting to this style
|
||||||
pub fn apply_formatting(&mut self, formatting: &ChatFormatting) {
|
pub fn apply_formatting(&mut self, formatting: &ChatFormatting) {
|
||||||
match formatting {
|
match *formatting {
|
||||||
&ChatFormatting::BOLD => self.bold = Some(true),
|
ChatFormatting::BOLD => self.bold = Some(true),
|
||||||
&ChatFormatting::ITALIC => self.italic = Some(true),
|
ChatFormatting::ITALIC => self.italic = Some(true),
|
||||||
&ChatFormatting::UNDERLINE => self.underlined = Some(true),
|
ChatFormatting::UNDERLINE => self.underlined = Some(true),
|
||||||
&ChatFormatting::STRIKETHROUGH => self.strikethrough = Some(true),
|
ChatFormatting::STRIKETHROUGH => self.strikethrough = Some(true),
|
||||||
&ChatFormatting::OBFUSCATED => self.obfuscated = Some(true),
|
ChatFormatting::OBFUSCATED => self.obfuscated = Some(true),
|
||||||
&ChatFormatting::RESET => {
|
ChatFormatting::RESET => self.reset = true,
|
||||||
self.color = None;
|
ChatFormatting {
|
||||||
self.bold = None;
|
|
||||||
self.italic = None;
|
|
||||||
self.underlined = None;
|
|
||||||
self.strikethrough = None;
|
|
||||||
self.obfuscated = None;
|
|
||||||
}
|
|
||||||
&ChatFormatting {
|
|
||||||
name: _,
|
name: _,
|
||||||
code: _,
|
code: _,
|
||||||
is_format: _,
|
is_format: _,
|
||||||
|
@ -401,6 +387,8 @@ impl Style {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::component::DEFAULT_STYLE;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -418,22 +406,15 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ansi_difference_should_reset() {
|
fn ansi_difference_should_reset() {
|
||||||
let style_a = Style {
|
let style_a = Style {
|
||||||
color: None,
|
|
||||||
bold: Some(true),
|
bold: Some(true),
|
||||||
italic: Some(true),
|
italic: Some(true),
|
||||||
underlined: None,
|
..Style::default()
|
||||||
strikethrough: None,
|
|
||||||
obfuscated: None,
|
|
||||||
};
|
};
|
||||||
let style_b = Style {
|
let style_b = Style {
|
||||||
color: None,
|
|
||||||
bold: Some(false),
|
bold: Some(false),
|
||||||
italic: None,
|
..Style::default()
|
||||||
underlined: None,
|
|
||||||
strikethrough: None,
|
|
||||||
obfuscated: None,
|
|
||||||
};
|
};
|
||||||
let ansi_difference = style_a.compare_ansi(&style_b);
|
let ansi_difference = style_a.compare_ansi(&style_b, &Style::default());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ansi_difference,
|
ansi_difference,
|
||||||
format!(
|
format!(
|
||||||
|
@ -446,25 +427,40 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ansi_difference_shouldnt_reset() {
|
fn ansi_difference_shouldnt_reset() {
|
||||||
let style_a = Style {
|
let style_a = Style {
|
||||||
color: None,
|
|
||||||
bold: Some(true),
|
bold: Some(true),
|
||||||
italic: None,
|
..Style::default()
|
||||||
underlined: None,
|
|
||||||
strikethrough: None,
|
|
||||||
obfuscated: None,
|
|
||||||
};
|
};
|
||||||
let style_b = Style {
|
let style_b = Style {
|
||||||
color: None,
|
|
||||||
bold: None,
|
|
||||||
italic: Some(true),
|
italic: Some(true),
|
||||||
underlined: None,
|
..Style::default()
|
||||||
strikethrough: None,
|
|
||||||
obfuscated: None,
|
|
||||||
};
|
};
|
||||||
let ansi_difference = style_a.compare_ansi(&style_b);
|
let ansi_difference = style_a.compare_ansi(&style_b, &Style::default());
|
||||||
assert_eq!(ansi_difference, Ansi::ITALIC)
|
assert_eq!(ansi_difference, Ansi::ITALIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ansi_difference_explicit_reset() {
|
||||||
|
let style_a = Style {
|
||||||
|
bold: Some(true),
|
||||||
|
..Style::empty()
|
||||||
|
};
|
||||||
|
let style_b = Style {
|
||||||
|
italic: Some(true),
|
||||||
|
reset: true,
|
||||||
|
..Style::empty()
|
||||||
|
};
|
||||||
|
let ansi_difference = style_a.compare_ansi(&style_b, &DEFAULT_STYLE);
|
||||||
|
assert_eq!(
|
||||||
|
ansi_difference,
|
||||||
|
format!(
|
||||||
|
"{reset}{italic}{white}",
|
||||||
|
reset = Ansi::RESET,
|
||||||
|
white = Ansi::rgb(ChatFormatting::WHITE.color.unwrap()),
|
||||||
|
italic = Ansi::ITALIC
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_code() {
|
fn test_from_code() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
use crate::{base_component::BaseComponent, component::Component, style::ChatFormatting};
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
base_component::BaseComponent,
|
||||||
|
component::Component,
|
||||||
|
style::{ChatFormatting, Style, TextColor},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TextComponent {
|
pub struct TextComponent {
|
||||||
|
@ -10,7 +16,7 @@ const LEGACY_FORMATTING_CODE_SYMBOL: char = '§';
|
||||||
|
|
||||||
/// Convert a legacy color code string into a Component
|
/// Convert a legacy color code string into a Component
|
||||||
/// Technically in Minecraft this is done when displaying the text, but AFAIK it's the same as just doing it in TextComponent
|
/// Technically in Minecraft this is done when displaying the text, but AFAIK it's the same as just doing it in TextComponent
|
||||||
pub fn legacy_color_code_to_component(legacy_color_code: &str) -> Component {
|
pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextComponent {
|
||||||
let mut components: Vec<TextComponent> = Vec::with_capacity(1);
|
let mut components: Vec<TextComponent> = Vec::with_capacity(1);
|
||||||
// iterate over legacy_color_code, if it starts with LEGACY_COLOR_CODE_SYMBOL then read the next character and get the style from that
|
// iterate over legacy_color_code, if it starts with LEGACY_COLOR_CODE_SYMBOL then read the next character and get the style from that
|
||||||
// otherwise, add the character to the text
|
// otherwise, add the character to the text
|
||||||
|
@ -21,24 +27,15 @@ pub fn legacy_color_code_to_component(legacy_color_code: &str) -> Component {
|
||||||
if legacy_color_code.chars().nth(i).unwrap() == LEGACY_FORMATTING_CODE_SYMBOL {
|
if legacy_color_code.chars().nth(i).unwrap() == LEGACY_FORMATTING_CODE_SYMBOL {
|
||||||
let formatting_code = legacy_color_code.chars().nth(i + 1).unwrap();
|
let formatting_code = legacy_color_code.chars().nth(i + 1).unwrap();
|
||||||
if let Ok(formatter) = ChatFormatting::from_code(formatting_code) {
|
if let Ok(formatter) = ChatFormatting::from_code(formatting_code) {
|
||||||
if components.is_empty() || components.last().unwrap().text.is_empty() {
|
if components.is_empty() {
|
||||||
|
components.push(TextComponent::new("".to_string()));
|
||||||
|
} else if !components.last().unwrap().text.is_empty() {
|
||||||
components.push(TextComponent::new("".to_string()));
|
components.push(TextComponent::new("".to_string()));
|
||||||
}
|
}
|
||||||
println!(
|
|
||||||
"applying formatter {:?} {:?}",
|
let style = &mut components.last_mut().unwrap().base.style;
|
||||||
components.last_mut().unwrap().base.style,
|
// if the formatter is a reset, then we need to reset the style to the default
|
||||||
formatter
|
style.apply_formatting(formatter);
|
||||||
);
|
|
||||||
components
|
|
||||||
.last_mut()
|
|
||||||
.unwrap()
|
|
||||||
.base
|
|
||||||
.style
|
|
||||||
.apply_formatting(formatter);
|
|
||||||
println!(
|
|
||||||
"applied formatter {:?}",
|
|
||||||
components.last_mut().unwrap().base.style
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -60,36 +57,72 @@ pub fn legacy_color_code_to_component(legacy_color_code: &str) -> Component {
|
||||||
final_component.base.siblings.push(component.get());
|
final_component.base.siblings.push(component.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
final_component.get()
|
final_component
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextComponent {
|
impl<'a> TextComponent {
|
||||||
pub fn new(text: String) -> Self {
|
pub fn new(text: String) -> Self {
|
||||||
Self {
|
// if it contains a LEGACY_FORMATTING_CODE_SYMBOL, format it
|
||||||
base: BaseComponent::new(),
|
if text.contains(LEGACY_FORMATTING_CODE_SYMBOL) {
|
||||||
text,
|
legacy_color_code_to_text_component(&text)
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
base: BaseComponent::new(),
|
||||||
|
text,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
self.text.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(self) -> Component {
|
fn get(self) -> Component {
|
||||||
Component::Text(self)
|
Component::Text(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TextComponent {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.text.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::style::Ansi;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_legacy_color_code_to_component() {
|
fn test_hypixel_motd() {
|
||||||
let component = legacy_color_code_to_component("§lHello §r§1w§2o§3r§4l§5d");
|
let component =
|
||||||
|
TextComponent::new("§aHypixel Network §c[1.8-1.18]\n§b§lHAPPY HOLIDAYS".to_string())
|
||||||
|
.get();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
component.to_ansi(),
|
component.to_ansi(None),
|
||||||
"\u{1b}[38;2;170;0;170mHello world\u{1b}[m"
|
format!(
|
||||||
|
"{GREEN}Hypixel Network {RED}[1.8-1.18]\n{BOLD}{AQUA}HAPPY HOLIDAYS{RESET}",
|
||||||
|
GREEN = Ansi::rgb(ChatFormatting::GREEN.color.unwrap()),
|
||||||
|
RED = Ansi::rgb(ChatFormatting::RED.color.unwrap()),
|
||||||
|
AQUA = Ansi::rgb(ChatFormatting::AQUA.color.unwrap()),
|
||||||
|
BOLD = Ansi::BOLD,
|
||||||
|
RESET = Ansi::RESET
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_legacy_color_code_to_component() {
|
||||||
|
let component = TextComponent::new("§lHello §r§1w§2o§3r§4l§5d".to_string()).get();
|
||||||
|
assert_eq!(
|
||||||
|
component.to_ansi(None),
|
||||||
|
format!(
|
||||||
|
"{BOLD}Hello {RESET}{DARK_BLUE}w{DARK_GREEN}o{DARK_AQUA}r{DARK_RED}l{DARK_PURPLE}d{RESET}",
|
||||||
|
BOLD = Ansi::BOLD,
|
||||||
|
RESET = Ansi::RESET,
|
||||||
|
DARK_BLUE = Ansi::rgb(ChatFormatting::DARK_BLUE.color.unwrap()),
|
||||||
|
DARK_GREEN = Ansi::rgb(ChatFormatting::DARK_GREEN.color.unwrap()),
|
||||||
|
DARK_AQUA = Ansi::rgb(ChatFormatting::DARK_AQUA.color.unwrap()),
|
||||||
|
DARK_RED = Ansi::rgb(ChatFormatting::DARK_RED.color.unwrap()),
|
||||||
|
DARK_PURPLE = Ansi::rgb(ChatFormatting::DARK_PURPLE.color.unwrap())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use minecraft_chat::{
|
||||||
component::Component,
|
component::Component,
|
||||||
style::{Ansi, ChatFormatting, TextColor},
|
style::{Ansi, ChatFormatting, TextColor},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -14,9 +15,9 @@ fn basic_ansi_test() {
|
||||||
}"#,
|
}"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let component = Component::new(&j).unwrap();
|
let component = Component::deserialize(&j).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
component.to_ansi(),
|
component.to_ansi(None),
|
||||||
"\u{1b}[1m\u{1b}[38;2;255;85;85mhello\u{1b}[m"
|
"\u{1b}[1m\u{1b}[38;2;255;85;85mhello\u{1b}[m"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -50,9 +51,9 @@ fn complex_ansi_test() {
|
||||||
]"##,
|
]"##,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let component = Component::new(&j).unwrap();
|
let component = Component::deserialize(&j).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
component.to_ansi(),
|
component.to_ansi(None),
|
||||||
format!(
|
format!(
|
||||||
"{bold}{italic}{underlined}{red}hello{reset}{bold}{italic}{red} {reset}{italic}{strikethrough}{abcdef}world{reset}{abcdef} asdf{bold}!{reset}",
|
"{bold}{italic}{underlined}{red}hello{reset}{bold}{italic}{red} {reset}{italic}{strikethrough}{abcdef}world{reset}{abcdef} asdf{bold}!{reset}",
|
||||||
bold = Ansi::BOLD,
|
bold = Ansi::BOLD,
|
||||||
|
@ -69,6 +70,6 @@ fn complex_ansi_test() {
|
||||||
#[test]
|
#[test]
|
||||||
fn component_from_string() {
|
fn component_from_string() {
|
||||||
let j: Value = serde_json::from_str("\"foo\"").unwrap();
|
let j: Value = serde_json::from_str("\"foo\"").unwrap();
|
||||||
let component = Component::new(&j).unwrap();
|
let component = Component::deserialize(&j).unwrap();
|
||||||
assert_eq!(component.to_ansi(), "foo");
|
assert_eq!(component.to_ansi(None), "foo");
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,13 @@ version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-recursion = "^0.3.2"
|
async-recursion = "^0.3.2"
|
||||||
|
async-trait = "0.1.51"
|
||||||
byteorder = "^1.4.3"
|
byteorder = "^1.4.3"
|
||||||
bytes = "^1.1.0"
|
bytes = "^1.1.0"
|
||||||
|
minecraft-chat = {path = "../minecraft-chat"}
|
||||||
|
serde = {version = "1.0.130", features = ["serde_derive"]}
|
||||||
|
serde_json = "^1.0.72"
|
||||||
thiserror = "^1.0.30"
|
thiserror = "^1.0.30"
|
||||||
tokio = {version = "^1.14.0", features = ["io-util", "net", "macros"]}
|
tokio = {version = "^1.14.0", features = ["io-util", "net", "macros"]}
|
||||||
tokio-util = "^0.6.9"
|
tokio-util = "^0.6.9"
|
||||||
trust-dns-resolver = "^0.20.3"
|
trust-dns-resolver = "^0.20.3"
|
||||||
async-trait = "0.1.51"
|
|
||||||
minecraft-chat = { path = "../minecraft-chat" }
|
|
||||||
serde_json = "^1.0.72"
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl Connection {
|
||||||
self.state = state;
|
self.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_packet(&mut self) -> Result<(), String> {
|
pub async fn read_packet(&mut self) -> Result<Packet, String> {
|
||||||
// what this does:
|
// what this does:
|
||||||
// 1. reads the first 5 bytes, probably only some of this will be used to get the packet length
|
// 1. reads the first 5 bytes, probably only some of this will be used to get the packet length
|
||||||
// 2. how much we should read = packet length - 5
|
// 2. how much we should read = packet length - 5
|
||||||
|
@ -69,9 +69,7 @@ impl Connection {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("packet: {:?}", packet);
|
Ok(packet)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a packet to the server
|
/// Write a packet to the server
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub struct ServerIpAddress {
|
||||||
|
|
||||||
impl ServerAddress {
|
impl ServerAddress {
|
||||||
/// Convert a Minecraft server address (host:port, the port is optional) to a ServerAddress
|
/// Convert a Minecraft server address (host:port, the port is optional) to a ServerAddress
|
||||||
pub fn parse(string: &String) -> Result<ServerAddress, String> {
|
pub fn parse(string: &str) -> Result<ServerAddress, String> {
|
||||||
if string.is_empty() {
|
if string.is_empty() {
|
||||||
return Err("Empty string".to_string());
|
return Err("Empty string".to_string());
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub async fn read_varint<T: AsyncRead + std::marker::Unpin>(
|
||||||
pub fn write_varint(buf: &mut Vec<u8>, mut value: i32) {
|
pub fn write_varint(buf: &mut Vec<u8>, mut value: i32) {
|
||||||
let mut buffer = [0];
|
let mut buffer = [0];
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
buf.write(&buffer).unwrap();
|
buf.write_all(&buffer).unwrap();
|
||||||
}
|
}
|
||||||
while value != 0 {
|
while value != 0 {
|
||||||
buffer[0] = (value & 0b0111_1111) as u8;
|
buffer[0] = (value & 0b0111_1111) as u8;
|
||||||
|
@ -57,7 +57,7 @@ pub fn write_varint(buf: &mut Vec<u8>, mut value: i32) {
|
||||||
if value != 0 {
|
if value != 0 {
|
||||||
buffer[0] |= 0b1000_0000;
|
buffer[0] |= 0b1000_0000;
|
||||||
}
|
}
|
||||||
buf.write(&buffer).unwrap();
|
buf.write_all(&buffer).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ pub async fn read_utf_with_len<T: AsyncRead + std::marker::Unpin>(
|
||||||
Ok(string)
|
Ok(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_utf_with_len(buf: &mut Vec<u8>, string: &String, len: usize) {
|
pub fn write_utf_with_len(buf: &mut Vec<u8>, string: &str, len: usize) {
|
||||||
if string.len() > len {
|
if string.len() > len {
|
||||||
panic!(
|
panic!(
|
||||||
"String too big (was {} bytes encoded, max {})",
|
"String too big (was {} bytes encoded, max {})",
|
||||||
|
@ -152,7 +152,7 @@ pub async fn read_utf<T: AsyncRead + std::marker::Unpin>(
|
||||||
read_utf_with_len(buf, MAX_STRING_LENGTH.into()).await
|
read_utf_with_len(buf, MAX_STRING_LENGTH.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_utf(buf: &mut Vec<u8>, string: &String) {
|
pub fn write_utf(buf: &mut Vec<u8>, string: &str) {
|
||||||
write_utf_with_len(buf, string, MAX_STRING_LENGTH.into());
|
write_utf_with_len(buf, string, MAX_STRING_LENGTH.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use minecraft_chat::component::Component;
|
use minecraft_chat::component::Component;
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::io::BufReader;
|
use tokio::io::BufReader;
|
||||||
|
|
||||||
|
@ -8,29 +9,29 @@ use crate::{
|
||||||
packets::{Packet, PacketTrait},
|
packets::{Packet, PacketTrait},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct Version {
|
pub struct Version {
|
||||||
name: String,
|
pub name: String,
|
||||||
protocol: u32,
|
pub protocol: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct SamplePlayer {
|
pub struct SamplePlayer {
|
||||||
id: String,
|
pub id: String,
|
||||||
name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
struct Players {
|
pub struct Players {
|
||||||
max: u32,
|
pub max: u32,
|
||||||
online: u32,
|
pub online: u32,
|
||||||
sample: Vec<SamplePlayer>,
|
pub sample: Vec<SamplePlayer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct ClientboundStatusResponsePacket {
|
pub struct ClientboundStatusResponsePacket {
|
||||||
// version: Version,
|
pub version: Version,
|
||||||
description: Component,
|
pub description: Component,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -47,13 +48,9 @@ impl PacketTrait for ClientboundStatusResponsePacket {
|
||||||
let status_string = mc_buf::read_utf(buf).await?;
|
let status_string = mc_buf::read_utf(buf).await?;
|
||||||
let status_json: Value =
|
let status_json: Value =
|
||||||
serde_json::from_str(status_string.as_str()).expect("Server status isn't valid JSON");
|
serde_json::from_str(status_string.as_str()).expect("Server status isn't valid JSON");
|
||||||
let description_string: &Value = status_json.get("description").unwrap();
|
|
||||||
|
|
||||||
// this.status = GsonHelper.fromJson(GSON, friendlyByteBuf.readUtf(32767), ServerStatus.class);
|
Ok(ClientboundStatusResponsePacket::deserialize(status_json)
|
||||||
Ok(ClientboundStatusResponsePacket {
|
.map_err(|e| e.to_string())?
|
||||||
// version: status_json.get("version"),
|
.get())
|
||||||
description: Component::new(description_string)?,
|
|
||||||
}
|
|
||||||
.get())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,7 @@ pub async fn resolve_address(address: &ServerAddress) -> Result<ServerIpAddress,
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// if it resolves that means it's a redirect so we call resolve_address again with the new host
|
// if it resolves that means it's a redirect so we call resolve_address again with the new host
|
||||||
if srv_redirect_result.is_ok() {
|
if let Ok(redirect_result) = srv_redirect_result {
|
||||||
let redirect_result = srv_redirect_result.unwrap();
|
|
||||||
let redirect_srv = redirect_result
|
let redirect_srv = redirect_result
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.next()
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
packets::{
|
packets::{
|
||||||
handshake::client_intention_packet::ClientIntentionPacket,
|
handshake::client_intention_packet::ClientIntentionPacket,
|
||||||
status::serverbound_status_request_packet::ServerboundStatusRequestPacket,
|
status::serverbound_status_request_packet::ServerboundStatusRequestPacket,
|
||||||
ConnectionProtocol, PacketTrait,
|
ConnectionProtocol, Packet, PacketTrait,
|
||||||
},
|
},
|
||||||
resolver, ServerAddress,
|
resolver, ServerAddress,
|
||||||
};
|
};
|
||||||
|
@ -33,7 +33,17 @@ pub async fn ping_server(address: &ServerAddress) -> Result<(), String> {
|
||||||
conn.send_packet(ServerboundStatusRequestPacket {}.get())
|
conn.send_packet(ServerboundStatusRequestPacket {}.get())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
conn.read_packet().await.unwrap();
|
let packet = conn.read_packet().await.unwrap();
|
||||||
|
|
||||||
|
match packet {
|
||||||
|
Packet::ClientboundStatusResponsePacket(p) => {
|
||||||
|
println!("{:?}", p);
|
||||||
|
println!("{}", p.description.to_ansi(None));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("unexpected packet {:?}", packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue