Expand TextComponent
This commit is contained in:
parent
b4abc6b297
commit
5f3a4d34d1
@ -1,13 +1,47 @@
|
|||||||
use core::str;
|
use core::str;
|
||||||
|
|
||||||
use fastnbt::SerOpts;
|
use fastnbt::SerOpts;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Text(Box<TextComponent>);
|
||||||
|
|
||||||
// Fepresents a Text component
|
// Fepresents a Text component
|
||||||
// Reference: https://wiki.vg/Text_formatting#Text_components
|
// Reference: https://wiki.vg/Text_formatting#Text_components
|
||||||
#[derive(Clone, PartialEq, Default, Debug, Deserialize)]
|
#[derive(Clone, Default, Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextComponent {
|
pub struct TextComponent {
|
||||||
pub text: String,
|
/// The actual text
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub content: TextContent,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(flatten)]
|
||||||
|
/// Changes the color to render the content
|
||||||
|
pub color: Option<Color>,
|
||||||
|
/// Whether to render the content in bold.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bold: Option<u8>,
|
||||||
|
/// Whether to render the content in italic.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub italic: Option<u8>,
|
||||||
|
/// Whether to render the content in underlined.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub underlined: Option<u8>,
|
||||||
|
/// Whether to render the content in strikethrough.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub strikethrough: Option<u8>,
|
||||||
|
/// Whether to render the content in obfuscated.
|
||||||
|
/// Keep in mind that booleans are representet as bytes in nbt
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub obfuscated: Option<u8>,
|
||||||
|
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub insertion: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::Serialize for TextComponent {
|
impl serde::Serialize for TextComponent {
|
||||||
@ -20,33 +54,152 @@ impl serde::Serialize for TextComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextComponent {
|
impl TextComponent {
|
||||||
pub fn encode(&self) -> Vec<u8> {
|
pub fn color(mut self, color: Color) -> Self {
|
||||||
#[derive(serde::Serialize)]
|
self.color = Some(color);
|
||||||
struct TempStruct<'a> {
|
self
|
||||||
text: &'a String,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fastnbt::to_bytes_with_opts(&TempStruct { text: &self.text }, SerOpts::network_nbt())
|
pub fn color_named(mut self, color: NamedColor) -> Self {
|
||||||
.unwrap()
|
self.color = Some(Color::Named(color));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text bold
|
||||||
|
pub fn bold(mut self) -> Self {
|
||||||
|
self.bold = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text italic
|
||||||
|
pub fn italic(mut self) -> Self {
|
||||||
|
self.italic = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text underlined
|
||||||
|
pub fn underlined(mut self) -> Self {
|
||||||
|
self.underlined = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text strikethrough
|
||||||
|
pub fn strikethrough(mut self) -> Self {
|
||||||
|
self.strikethrough = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes the text obfuscated
|
||||||
|
pub fn obfuscated(mut self) -> Self {
|
||||||
|
self.obfuscated = Some(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
|
||||||
|
pub fn insertion(mut self, text: String) -> Self {
|
||||||
|
self.insertion = Some(text);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(&self) -> Vec<u8> {
|
||||||
|
// TODO: Somehow fix this ugly mess
|
||||||
|
#[derive(serde::Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TempStruct<'a> {
|
||||||
|
#[serde(flatten)]
|
||||||
|
text: &'a TextContent,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub color: &'a Option<Color>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bold: &'a Option<u8>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub italic: &'a Option<u8>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub underlined: &'a Option<u8>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub strikethrough: &'a Option<u8>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub obfuscated: &'a Option<u8>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub insertion: &'a Option<String>,
|
||||||
|
}
|
||||||
|
let astruct = TempStruct {
|
||||||
|
text: &self.content,
|
||||||
|
color: &self.color,
|
||||||
|
bold: &self.bold,
|
||||||
|
italic: &self.italic,
|
||||||
|
underlined: &self.underlined,
|
||||||
|
strikethrough: &self.strikethrough,
|
||||||
|
obfuscated: &self.obfuscated,
|
||||||
|
insertion: &self.insertion,
|
||||||
|
};
|
||||||
|
let nbt = fastnbt::to_bytes_with_opts(&astruct, SerOpts::network_nbt()).unwrap();
|
||||||
|
nbt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for TextComponent {
|
impl From<String> for TextComponent {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
Self { text: value }
|
Self {
|
||||||
|
content: TextContent::Text { text: value },
|
||||||
|
color: None,
|
||||||
|
bold: None,
|
||||||
|
italic: None,
|
||||||
|
underlined: None,
|
||||||
|
strikethrough: None,
|
||||||
|
obfuscated: None,
|
||||||
|
insertion: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for TextComponent {
|
impl From<&str> for TextComponent {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: value.to_string(),
|
content: TextContent::Text {
|
||||||
|
text: value.to_string(),
|
||||||
|
},
|
||||||
|
color: None,
|
||||||
|
bold: None,
|
||||||
|
italic: None,
|
||||||
|
underlined: None,
|
||||||
|
strikethrough: None,
|
||||||
|
obfuscated: None,
|
||||||
|
insertion: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum TextContent {
|
||||||
|
/// Raw Text
|
||||||
|
Text { text: String },
|
||||||
|
/// Translated text
|
||||||
|
Translate {
|
||||||
|
translate: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
with: Vec<Text>,
|
||||||
|
},
|
||||||
|
/// Displays the name of one or more entities found by a selector.
|
||||||
|
EntityNames {
|
||||||
|
selector: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
separator: Option<Text>,
|
||||||
|
},
|
||||||
|
/// A keybind identifier
|
||||||
|
/// https://minecraft.fandom.com/wiki/Controls#Configurable_controls
|
||||||
|
Keybind { keybind: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextContent {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Text { text: "".into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Text color
|
/// Text color
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
/// The default color for the text will be used, which varies by context
|
/// The default color for the text will be used, which varies by context
|
||||||
/// (in some cases, it's white; in others, it's black; in still others, it
|
/// (in some cases, it's white; in others, it's black; in still others, it
|
||||||
@ -58,22 +211,23 @@ pub enum Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Named Minecraft color
|
/// Named Minecraft color
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum NamedColor {
|
pub enum NamedColor {
|
||||||
Black = 0,
|
Black = 0,
|
||||||
DarkBlue,
|
DarkBlue,
|
||||||
DarkGreen,
|
DarkGreen,
|
||||||
DarkCyan,
|
DarkAqua,
|
||||||
DarkRed,
|
DarkRed,
|
||||||
Purple,
|
DarkPurple,
|
||||||
Gold,
|
Gold,
|
||||||
Gray,
|
Gray,
|
||||||
DarkGray,
|
DarkGray,
|
||||||
Blue,
|
Blue,
|
||||||
BrightGreen,
|
Green,
|
||||||
Cyan,
|
Aqua,
|
||||||
Red,
|
Red,
|
||||||
Pink,
|
LightPurple,
|
||||||
Yellow,
|
Yellow,
|
||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -343,10 +343,8 @@ impl Client {
|
|||||||
.unwrap_or_else(|_| self.close());
|
.unwrap_or_else(|_| self.close());
|
||||||
}
|
}
|
||||||
ConnectionState::Play => {
|
ConnectionState::Play => {
|
||||||
self.try_send_packet(CPlayDisconnect::new(TextComponent {
|
self.try_send_packet(CPlayDisconnect::new(TextComponent::from(reason)))
|
||||||
text: reason.to_string(),
|
.unwrap_or_else(|_| self.close());
|
||||||
}))
|
|
||||||
.unwrap_or_else(|_| self.close());
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("Cant't kick in {:?} State", self.connection_state)
|
log::warn!("Cant't kick in {:?} State", self.connection_state)
|
||||||
|
|||||||
@ -31,7 +31,7 @@ impl<'a> CommandSender<'a> {
|
|||||||
pub fn send_message(&mut self, text: TextComponent) {
|
pub fn send_message(&mut self, text: TextComponent) {
|
||||||
match self {
|
match self {
|
||||||
// TODO: add color and stuff to console
|
// TODO: add color and stuff to console
|
||||||
CommandSender::Console => log::info!("{}", text.text),
|
CommandSender::Console => log::info!("{:?}", text.content),
|
||||||
CommandSender::Player(c) => c.send_system_message(text),
|
CommandSender::Player(c) => c.send_system_message(text),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,92 @@ impl<'a> Command<'a> for PumpkinCommand {
|
|||||||
fn on_execute(sender: &mut super::CommandSender<'a>, _command: String) {
|
fn on_execute(sender: &mut super::CommandSender<'a>, _command: String) {
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
let description = env!("CARGO_PKG_DESCRIPTION");
|
let description = env!("CARGO_PKG_DESCRIPTION");
|
||||||
sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")))
|
// sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(pumpkin_protocol::text::NamedColor::Green))
|
||||||
|
|
||||||
|
// test
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("Pumpkin")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Black)
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("is")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::DarkBlue)
|
||||||
|
.italic(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("such")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::DarkGreen)
|
||||||
|
.underlined(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("a")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::DarkAqua)
|
||||||
|
.strikethrough(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("super")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::DarkRed)
|
||||||
|
.obfuscated(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("mega")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::DarkPurple)
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("great")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Gold)
|
||||||
|
.italic(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("project")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Gray)
|
||||||
|
.underlined(),
|
||||||
|
);
|
||||||
|
sender.send_message(TextComponent::from(""));
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("no")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::DarkGray)
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("worries")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Blue)
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("there")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Green)
|
||||||
|
.underlined(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("will")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Aqua)
|
||||||
|
.italic(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("be")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Red)
|
||||||
|
.obfuscated(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("chunk")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::LightPurple)
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("loading")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::Yellow)
|
||||||
|
.strikethrough(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("soon")
|
||||||
|
.color_named(pumpkin_protocol::text::NamedColor::White)
|
||||||
|
.bold(),
|
||||||
|
);
|
||||||
|
sender.send_message(
|
||||||
|
TextComponent::from("soon").color_named(pumpkin_protocol::text::NamedColor::White),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user