diff options
| -rw-r--r-- | bot/Cargo.toml | 2 | ||||
| -rw-r--r-- | minecraft-chat/Cargo.toml | 2 | ||||
| -rw-r--r-- | minecraft-chat/src/style.rs | 177 | ||||
| -rw-r--r-- | minecraft-chat/tests/integration_test.rs | 59 | ||||
| -rw-r--r-- | minecraft-client/Cargo.toml | 2 | ||||
| -rw-r--r-- | minecraft-protocol/Cargo.toml | 2 |
6 files changed, 148 insertions, 96 deletions
diff --git a/bot/Cargo.toml b/bot/Cargo.toml index 75c6e835..f1c89a99 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bot" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/minecraft-chat/Cargo.toml b/minecraft-chat/Cargo.toml index bca63666..2578a535 100644 --- a/minecraft-chat/Cargo.toml +++ b/minecraft-chat/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2018" +edition = "2021" name = "minecraft-chat" version = "0.1.0" diff --git a/minecraft-chat/src/style.rs b/minecraft-chat/src/style.rs index dfbfe8ce..66701a4b 100644 --- a/minecraft-chat/src/style.rs +++ b/minecraft-chat/src/style.rs @@ -4,34 +4,17 @@ use serde_json::Value; #[derive(Clone, PartialEq, Debug)] pub struct TextColor { - value: u32, - name: Option<String>, + pub value: u32, + pub name: Option<String>, } impl TextColor { - pub fn parse(value: String) -> Result<TextColor, String> { - // if (string.startsWith("#")) { - // try { - // int n = Integer.parseInt(string.substring(1), 16); - // return TextColor.fromRgb(n); - // } - // catch (NumberFormatException numberFormatException) { - // return null; - // } - // } - // return NAMED_COLORS.get(string); - if value.starts_with("#") { - let n = value.chars().skip(1).collect::<String>(); - let n = u32::from_str_radix(&n, 16).unwrap(); - return Ok(TextColor::from_rgb(n)); - } - // private static final Map<ChatFormatting, TextColor> LEGACY_FORMAT_TO_COLOR = (Map)Stream.of(ChatFormatting.values()).filter(ChatFormatting::isColor).collect(ImmutableMap.toImmutableMap(Function.identity(), chatFormatting -> new TextColor(chatFormatting.getColor(), chatFormatting.getName()))); - // private static final Map<String, TextColor> NAMED_COLORS = (Map)LEGACY_FORMAT_TO_COLOR.values().stream().collect(ImmutableMap.toImmutableMap(textColor -> textColor.name, Function.identity())); - let mut LEGACY_FORMAT_TO_COLOR = HashMap::new(); - let mut NAMED_COLORS = HashMap::new(); + // hopefully rust/llvm optimizes this so it's just calculated once + fn calculate_legacy_format_to_color() -> HashMap<&'static ChatFormatting<'static>, TextColor> { + let mut legacy_format_to_color = HashMap::new(); for formatter in &ChatFormatting::FORMATTERS { if !formatter.is_format && *formatter != ChatFormatting::RESET { - LEGACY_FORMAT_TO_COLOR.insert( + legacy_format_to_color.insert( formatter, TextColor { value: formatter.color.unwrap(), @@ -40,10 +23,26 @@ impl TextColor { ); } } - for color in LEGACY_FORMAT_TO_COLOR.values() { - NAMED_COLORS.insert(color.name.as_ref().unwrap(), color.clone()); + legacy_format_to_color + } + + fn calculate_named_colors() -> HashMap<String, TextColor> { + let legacy_format_to_color = Self::calculate_legacy_format_to_color(); + let mut named_colors = HashMap::new(); + for color in legacy_format_to_color.values() { + named_colors.insert(color.name.clone().unwrap(), color.clone()); + } + named_colors + } + + pub fn parse(value: String) -> Result<TextColor, String> { + if value.starts_with("#") { + let n = value.chars().skip(1).collect::<String>(); + let n = u32::from_str_radix(&n, 16).unwrap(); + return Ok(TextColor::from_rgb(n)); } - let color = NAMED_COLORS.get(&value.to_ascii_uppercase()); + let named_colors = Self::calculate_named_colors(); + let color = named_colors.get(&value.to_ascii_uppercase()); if color.is_some() { return Ok(color.unwrap().clone()); } @@ -71,48 +70,70 @@ mod tests { const PREFIX_CODE: char = '\u{00a7}'; #[derive(Clone, PartialEq, Eq, Hash, Debug)] -struct ChatFormatting<'a> { - name: &'a str, - code: char, - is_format: bool, - id: i32, - color: Option<u32>, +pub struct ChatFormatting<'a> { + pub name: &'a str, + pub code: char, + pub is_format: bool, + pub id: i32, + pub color: Option<u32>, +} + +pub struct Ansi {} +impl Ansi { + pub const BOLD: &'static str = "\x1b[1m"; + pub const ITALIC: &'static str = "\x1b[3m"; + pub const UNDERLINED: &'static str = "\x1b[4m"; + pub const STRIKETHROUGH: &'static str = "\x1b[9m"; + pub const OBFUSCATED: &'static str = "\x1b[8m"; + pub const RESET: &'static str = "\x1b[m"; + + pub fn rgb(value: u32) -> String { + format!( + "\x1b[38;2;{};{};{}m", + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF + ) + } } impl<'a> ChatFormatting<'a> { - const BLACK: ChatFormatting<'a> = ChatFormatting::new("BLACK", '0', false, 0, Some(0)); - const DARK_BLUE: ChatFormatting<'a> = + pub const BLACK: ChatFormatting<'a> = ChatFormatting::new("BLACK", '0', false, 0, Some(0)); + pub const DARK_BLUE: ChatFormatting<'a> = ChatFormatting::new("DARK_BLUE", '1', false, 1, Some(170)); - const DARK_GREEN: ChatFormatting<'a> = + pub const DARK_GREEN: ChatFormatting<'a> = ChatFormatting::new("DARK_GREEN", '2', false, 2, Some(43520)); - const DARK_AQUA: ChatFormatting<'a> = + pub const DARK_AQUA: ChatFormatting<'a> = ChatFormatting::new("DARK_AQUA", '3', false, 3, Some(43690)); - const DARK_RED: ChatFormatting<'a> = + pub const DARK_RED: ChatFormatting<'a> = ChatFormatting::new("DARK_RED", '4', false, 4, Some(1114112)); - const DARK_PURPLE: ChatFormatting<'a> = + pub const DARK_PURPLE: ChatFormatting<'a> = ChatFormatting::new("DARK_PURPLE", '5', false, 5, Some(11141290)); - const GOLD: ChatFormatting<'a> = ChatFormatting::new("GOLD", '6', false, 6, Some(16755200)); - const GRAY: ChatFormatting<'a> = ChatFormatting::new("GRAY", '7', false, 7, Some(11184810)); - const DARK_GRAY: ChatFormatting<'a> = + pub const GOLD: ChatFormatting<'a> = ChatFormatting::new("GOLD", '6', false, 6, Some(16755200)); + pub const GRAY: ChatFormatting<'a> = ChatFormatting::new("GRAY", '7', false, 7, Some(11184810)); + pub const DARK_GRAY: ChatFormatting<'a> = ChatFormatting::new("DARK_GRAY", '8', false, 8, Some(5592405)); - const BLUE: ChatFormatting<'a> = ChatFormatting::new("BLUE", '9', false, 9, Some(5592575)); - const GREEN: ChatFormatting<'a> = ChatFormatting::new("GREEN", 'a', false, 10, Some(5635925)); - const AQUA: ChatFormatting<'a> = ChatFormatting::new("AQUA", 'b', false, 11, Some(5636095)); - const RED: ChatFormatting<'a> = ChatFormatting::new("RED", 'c', false, 12, Some(16733525)); - const LIGHT_PURPLE: ChatFormatting<'a> = + pub const BLUE: ChatFormatting<'a> = ChatFormatting::new("BLUE", '9', false, 9, Some(5592575)); + pub const GREEN: ChatFormatting<'a> = + ChatFormatting::new("GREEN", 'a', false, 10, Some(5635925)); + pub const AQUA: ChatFormatting<'a> = ChatFormatting::new("AQUA", 'b', false, 11, Some(5636095)); + pub const RED: ChatFormatting<'a> = ChatFormatting::new("RED", 'c', false, 12, Some(16733525)); + pub const LIGHT_PURPLE: ChatFormatting<'a> = ChatFormatting::new("LIGHT_PURPLE", 'd', false, 13, Some(16733695)); - const YELLOW: ChatFormatting<'a> = + pub const YELLOW: ChatFormatting<'a> = ChatFormatting::new("YELLOW", 'e', false, 14, Some(16777045)); - const WHITE: ChatFormatting<'a> = ChatFormatting::new("WHITE", 'f', false, 15, Some(16777215)); - const OBFUSCATED: ChatFormatting<'a> = ChatFormatting::new("OBFUSCATED", 'k', true, -1, None); - const STRIKETHROUGH: ChatFormatting<'a> = + pub const WHITE: ChatFormatting<'a> = + ChatFormatting::new("WHITE", 'f', false, 15, Some(16777215)); + pub const OBFUSCATED: ChatFormatting<'a> = + ChatFormatting::new("OBFUSCATED", 'k', true, -1, None); + pub const STRIKETHROUGH: ChatFormatting<'a> = ChatFormatting::new("STRIKETHROUGH", 'm', true, -1, None); - const BOLD: ChatFormatting<'a> = ChatFormatting::new("BOLD", 'l', true, -1, None); - const UNDERLINE: ChatFormatting<'a> = ChatFormatting::new("UNDERLINE", 'n', true, -1, None); - const ITALIC: ChatFormatting<'a> = ChatFormatting::new("ITALIC", 'o', true, -1, None); - const RESET: ChatFormatting<'a> = ChatFormatting::new("RESET", 'r', true, -1, None); + pub const BOLD: ChatFormatting<'a> = ChatFormatting::new("BOLD", 'l', true, -1, None); + pub const UNDERLINE: ChatFormatting<'a> = ChatFormatting::new("UNDERLINE", 'n', true, -1, None); + pub const ITALIC: ChatFormatting<'a> = ChatFormatting::new("ITALIC", 'o', true, -1, None); + pub const RESET: ChatFormatting<'a> = ChatFormatting::new("RESET", 'r', true, -1, None); - pub const FORMATTERS: [ChatFormatting<'a>; 16] = [ + pub const FORMATTERS: [ChatFormatting<'a>; 22] = [ ChatFormatting::BLACK, ChatFormatting::DARK_BLUE, ChatFormatting::DARK_GREEN, @@ -129,6 +150,12 @@ impl<'a> ChatFormatting<'a> { ChatFormatting::LIGHT_PURPLE, ChatFormatting::YELLOW, ChatFormatting::WHITE, + ChatFormatting::OBFUSCATED, + ChatFormatting::STRIKETHROUGH, + ChatFormatting::BOLD, + ChatFormatting::UNDERLINE, + ChatFormatting::ITALIC, + ChatFormatting::RESET, ]; const fn new( @@ -211,24 +238,6 @@ impl Style { } pub fn deserialize(json: &Value) -> Style { - // if (jsonElement.isJsonObject()) { - // JsonObject jsonObject = jsonElement.getAsJsonObject(); - // if (jsonObject == null) { - // return null; - // } - // Boolean bl = Serializer.getOptionalFlag(jsonObject, "bold"); - // Boolean bl2 = Serializer.getOptionalFlag(jsonObject, "italic"); - // Boolean bl3 = Serializer.getOptionalFlag(jsonObject, "underlined"); - // Boolean bl4 = Serializer.getOptionalFlag(jsonObject, "strikethrough"); - // Boolean bl5 = Serializer.getOptionalFlag(jsonObject, "obfuscated"); - // TextColor textColor = Serializer.getTextColor(jsonObject); - // String string = Serializer.getInsertion(jsonObject); - // ClickEvent clickEvent = Serializer.getClickEvent(jsonObject); - // HoverEvent hoverEvent = Serializer.getHoverEvent(jsonObject); - // ResourceLocation resourceLocation = Serializer.getFont(jsonObject); - // return new Style(textColor, bl, bl2, bl3, bl4, bl5, clickEvent, hoverEvent, string, resourceLocation); - // } - // return null; return if json.is_object() { let json_object = json.as_object().unwrap(); let bold = json_object.get("bold").and_then(|v| v.as_bool()); @@ -293,7 +302,7 @@ impl Style { let mut ansi_codes = String::new(); let before = if should_reset { - ansi_codes.push_str("\x1b[m"); + ansi_codes.push_str(Ansi::RESET); Style::new() } else { self.clone() @@ -301,23 +310,23 @@ impl Style { // 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) { - ansi_codes.push_str("\x1b[1m"); + ansi_codes.push_str(Ansi::BOLD); } // if italic used to be false/default and now it's true, set italic if !before.italic.unwrap_or(false) && after.italic.unwrap_or(false) { - ansi_codes.push_str("\x1b[3m"); + ansi_codes.push_str(Ansi::ITALIC); } // if underlined used to be false/default and now it's true, set underlined if !before.underlined.unwrap_or(false) && after.underlined.unwrap_or(false) { - ansi_codes.push_str("\x1b[4m"); + ansi_codes.push_str(Ansi::UNDERLINED); } // if strikethrough used to be false/default and now it's true, set strikethrough if !before.strikethrough.unwrap_or(false) && after.strikethrough.unwrap_or(false) { - ansi_codes.push_str("\x1b[9m"); + ansi_codes.push_str(Ansi::STRIKETHROUGH); } // if obfuscated used to be false/default and now it's true, set obfuscated if !before.obfuscated.unwrap_or(false) && after.obfuscated.unwrap_or(false) { - ansi_codes.push_str("\x1b[8m"); + ansi_codes.push_str(Ansi::OBFUSCATED); } // if the new color is different and not none, set color @@ -333,15 +342,7 @@ impl Style { if color_changed { let after_color = after.color.as_ref().unwrap(); - ansi_codes.push_str(&format!( - "\x1b[38;2;{};{};{}m", - // r - (after_color.value >> 16) & 0xFF, - // g - (after_color.value >> 8) & 0xFF, - // b - after_color.value & 0xFF - )); + ansi_codes.push_str(&Ansi::rgb(after_color.value)); } ansi_codes diff --git a/minecraft-chat/tests/integration_test.rs b/minecraft-chat/tests/integration_test.rs index 54426ed2..9119d58f 100644 --- a/minecraft-chat/tests/integration_test.rs +++ b/minecraft-chat/tests/integration_test.rs @@ -1,16 +1,67 @@ -use minecraft_chat::component::Component; +use minecraft_chat::{ + component::Component, + style::{Ansi, ChatFormatting, TextColor}, +}; use serde_json::{Result, Value}; #[test] -fn test() { +fn basic_ansi_test() { let j: Value = serde_json::from_str( r#"{ + "text": "hello", + "color": "red", + "bold": true +}"#, + ) + .unwrap(); + let component = Component::new(&j).unwrap(); + assert_eq!( + component.to_ansi(None), + "\x1b[1m\x1b[38;2;255;85;85mhello\x1b[m" + ); +} + +#[test] +fn complex_ansi_test() { + let j: Value = serde_json::from_str( + r##"[ + { "text": "hello", "color": "red", + "bold": true, + "italic": true, + "underlined": true, + "adsfsf": "this should be ignored", + "extra": [ + {"text": " ", "underlined": false}, + {"text": "world", "bold": false, "strikethrough": true, "color": "#abcdef"} + ] + }, + { + "text": " asdf", + "italic": false, + "obfuscated": "true", + "strikethrough": false + }, + { + "text": "!", "bold": true - }"#, + } +]"##, ) .unwrap(); let component = Component::new(&j).unwrap(); - assert_eq!(component.to_ansi(None), "\x1b[1m\x1b[38;2;255;85;85mhello\x1b[m"); + assert_eq!( + component.to_ansi(None), + format!( + "{bold}{italic}{underlined}{red}hello{reset}{bold}{italic} {reset}{italic}{strikethrough}{abcdef}world{reset} asdf{bold}!{reset}", + bold = Ansi::BOLD, + italic = Ansi::ITALIC, + underlined = Ansi::UNDERLINED, + red = Ansi::rgb(ChatFormatting::RED.color.unwrap()), + reset = Ansi::RESET, + strikethrough = Ansi::STRIKETHROUGH, + abcdef = Ansi::rgb(TextColor::parse("#abcdef".to_string()).unwrap().value), + ) + ); } diff --git a/minecraft-client/Cargo.toml b/minecraft-client/Cargo.toml index 034c8a6d..10a0ecbf 100644 --- a/minecraft-client/Cargo.toml +++ b/minecraft-client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "minecraft-client" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/minecraft-protocol/Cargo.toml b/minecraft-protocol/Cargo.toml index 971470d7..1716b826 100644 --- a/minecraft-protocol/Cargo.toml +++ b/minecraft-protocol/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2018" +edition = "2021" name = "minecraft-protocol" version = "0.1.0" |
