Create pumpkin-text

This commit is contained in:
Snowiiii 2024-08-09 10:41:10 +02:00
parent 5f3a4d34d1
commit f122b47e50
18 changed files with 162 additions and 141 deletions

12
Cargo.lock generated
View File

@ -1155,6 +1155,7 @@ dependencies = [
"pumpkin-entity", "pumpkin-entity",
"pumpkin-protocol", "pumpkin-protocol",
"pumpkin-registry", "pumpkin-registry",
"pumpkin-text",
"pumpkin-world", "pumpkin-world",
"rand", "rand",
"reqwest", "reqwest",
@ -1194,12 +1195,12 @@ dependencies = [
"aes", "aes",
"bytes", "bytes",
"cfb8", "cfb8",
"fastnbt 2.5.0 (git+https://github.com/owengage/fastnbt.git)",
"flate2", "flate2",
"log", "log",
"num-derive", "num-derive",
"num-traits", "num-traits",
"pumpkin-macros", "pumpkin-macros",
"pumpkin-text",
"serde", "serde",
"serde_json", "serde_json",
"take_mut", "take_mut",
@ -1217,6 +1218,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "pumpkin-text"
version = "0.1.0"
dependencies = [
"fastnbt 2.5.0 (git+https://github.com/owengage/fastnbt.git)",
"serde",
"uuid",
]
[[package]] [[package]]
name = "pumpkin-world" name = "pumpkin-world"
version = "0.1.0" version = "0.1.0"

View File

@ -1,6 +1,6 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ "pumpkin-entity", "pumpkin-macros/", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-world", "pumpkin/" ] members = [ "pumpkin-entity", "pumpkin-macros/", "pumpkin-protocol/", "pumpkin-registry/", "pumpkin-text", "pumpkin-world", "pumpkin/" ]
[workspace.package] [workspace.package]
version = "0.1.0" version = "0.1.0"

View File

@ -4,8 +4,8 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-macros = { path = "../pumpkin-macros" }
pumpkin-text = { path = "../pumpkin-text" }
bytes = "1.7" bytes = "1.7"
@ -22,9 +22,6 @@ log = "0.4"
num-traits = "0.2" num-traits = "0.2"
num-derive = "0.4" num-derive = "0.4"
# for text component
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
# encryption # encryption
aes = "0.8.4" aes = "0.8.4"
cfb8 = "0.8.1" cfb8 = "0.8.1"

View File

@ -1,6 +1,7 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use pumpkin_text::TextComponent;
use crate::{text::TextComponent, ClientPacket, VarInt}; use crate::{ClientPacket, VarInt};
#[derive(Clone)] #[derive(Clone)]
#[packet(0x1E)] #[packet(0x1E)]

View File

@ -1,8 +1,7 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use pumpkin_text::TextComponent;
use serde::Serialize; use serde::Serialize;
use crate::text::TextComponent;
#[derive(Serialize)] #[derive(Serialize)]
#[packet(0x1D)] #[packet(0x1D)]
pub struct CPlayDisconnect { pub struct CPlayDisconnect {

View File

@ -1,8 +1,9 @@
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use pumpkin_macros::packet; use pumpkin_macros::packet;
use pumpkin_text::TextComponent;
use serde::Serialize; use serde::Serialize;
use crate::{text::TextComponent, VarInt}; use crate::{ VarInt};
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
#[packet(0x39)] #[packet(0x39)]

View File

@ -1,8 +1,7 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use pumpkin_text::TextComponent;
use serde::Serialize; use serde::Serialize;
use crate::text::TextComponent;
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
#[packet(0x6C)] #[packet(0x6C)]
pub struct CSystemChatMessge { pub struct CSystemChatMessge {

View File

@ -8,7 +8,6 @@ use thiserror::Error;
pub mod bytebuf; pub mod bytebuf;
pub mod client; pub mod client;
pub mod server; pub mod server;
pub mod text;
pub mod packet_decoder; pub mod packet_decoder;
pub mod packet_encoder; pub mod packet_encoder;

9
pumpkin-text/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "pumpkin-text"
version.workspace = true
edition.workspace = true
[dependencies]
serde = { version = "1.0", features = ["derive"] }
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
uuid = "1.10"

19
pumpkin-text/src/click.rs Normal file
View File

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
/// Action to take on click of the text.
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "value", rename_all = "snake_case")]
pub enum ClickEvent {
/// Opens an URL
OpenUrl(String),
/// Works in signs, but only on the root text component
RunCommand(String),
/// Replaces the contents of the chat box with the text, not necessarily a
/// command.
SuggestCommand(String),
/// Only usable within written books. Changes the page of the book. Indexing
/// starts at 1.
ChangePage(i32),
/// Copies the given text to system clipboard
CopyToClipboard(String),
}

36
pumpkin-text/src/color.rs Normal file
View File

@ -0,0 +1,36 @@
use serde::{Deserialize, Serialize};
/// Text color
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Color {
/// 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
/// is a shade of gray that isn't normally used on text).
#[default]
Reset,
/// One of the 16 named Minecraft colors
Named(NamedColor),
}
/// Named Minecraft color
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NamedColor {
Black = 0,
DarkBlue,
DarkGreen,
DarkAqua,
DarkRed,
DarkPurple,
Gold,
Gray,
DarkGray,
Blue,
Green,
Aqua,
Red,
LightPurple,
Yellow,
White,
}

32
pumpkin-text/src/hover.rs Normal file
View File

@ -0,0 +1,32 @@
use serde::{Deserialize, Serialize};
use crate::Text;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "action", content = "contents", rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
pub enum HoverEvent {
/// Displays a tooltip with the given text.
ShowText(Text),
/// Shows an item.
ShowItem {
/// Resource identifier of the item
id: String,
/// Number of the items in the stack
count: Option<i32>,
/// NBT information about the item (sNBT format)
tag: String,
},
/// Shows an entity.
ShowEntity {
/// The entity's UUID
id: uuid::Uuid,
/// Resource identifier of the entity
#[serde(rename = "type")]
#[serde(default, skip_serializing_if = "Option::is_none")]
kind: Option<String>,
/// Optional custom name for the entity
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<Text>,
},
}

View File

@ -1,11 +1,18 @@
use core::str; use core::str;
use click::ClickEvent;
use color::Color;
use fastnbt::SerOpts; use fastnbt::SerOpts;
use hover::HoverEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod color;
pub mod click;
pub mod hover;
#[derive(Clone, Default, Debug, Serialize, Deserialize)] #[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct Text(Box<TextComponent>); pub struct Text(pub 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
@ -42,6 +49,12 @@ pub struct TextComponent {
/// 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 /// 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")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub insertion: Option<String>, pub insertion: Option<String>,
/// Allows for events to occur when the player clicks on text. Only work in chat.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub click_event: Option<ClickEvent>,
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hover_event: Option<HoverEvent>,
} }
impl serde::Serialize for TextComponent { impl serde::Serialize for TextComponent {
@ -59,7 +72,7 @@ impl TextComponent {
self self
} }
pub fn color_named(mut self, color: NamedColor) -> Self { pub fn color_named(mut self, color: color::NamedColor) -> Self {
self.color = Some(Color::Named(color)); self.color = Some(Color::Named(color));
self self
} }
@ -100,6 +113,18 @@ impl TextComponent {
self self
} }
/// Allows for events to occur when the player clicks on text. Only work in chat.
pub fn click_event(mut self, event: ClickEvent) -> Self {
self.click_event = Some(event);
self
}
/// Allows for a tooltip to be displayed when the player hovers their mouse over text.
pub fn hover_event(mut self, event: HoverEvent) -> Self {
self.hover_event = Some(event);
self
}
pub fn encode(&self) -> Vec<u8> { pub fn encode(&self) -> Vec<u8> {
// TODO: Somehow fix this ugly mess // TODO: Somehow fix this ugly mess
#[derive(serde::Serialize, Debug)] #[derive(serde::Serialize, Debug)]
@ -121,6 +146,10 @@ impl TextComponent {
pub obfuscated: &'a Option<u8>, pub obfuscated: &'a Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub insertion: &'a Option<String>, pub insertion: &'a Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub click_event: &'a Option<ClickEvent>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hover_event: &'a Option<HoverEvent>,
} }
let astruct = TempStruct { let astruct = TempStruct {
text: &self.content, text: &self.content,
@ -131,7 +160,10 @@ impl TextComponent {
strikethrough: &self.strikethrough, strikethrough: &self.strikethrough,
obfuscated: &self.obfuscated, obfuscated: &self.obfuscated,
insertion: &self.insertion, insertion: &self.insertion,
click_event: &self.click_event,
hover_event: &self.hover_event,
}; };
// dbg!(&serde_json::to_string(&astruct));
let nbt = fastnbt::to_bytes_with_opts(&astruct, SerOpts::network_nbt()).unwrap(); let nbt = fastnbt::to_bytes_with_opts(&astruct, SerOpts::network_nbt()).unwrap();
nbt nbt
} }
@ -148,6 +180,8 @@ impl From<String> for TextComponent {
strikethrough: None, strikethrough: None,
obfuscated: None, obfuscated: None,
insertion: None, insertion: None,
click_event: None,
hover_event: None,
} }
} }
} }
@ -165,6 +199,8 @@ impl From<&str> for TextComponent {
strikethrough: None, strikethrough: None,
obfuscated: None, obfuscated: None,
insertion: None, insertion: None,
click_event: None,
hover_event: None,
} }
} }
} }
@ -196,38 +232,3 @@ impl Default for TextContent {
Self::Text { text: "".into() } Self::Text { text: "".into() }
} }
} }
/// Text color
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Color {
/// 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
/// is a shade of gray that isn't normally used on text).
#[default]
Reset,
/// One of the 16 named Minecraft colors
Named(NamedColor),
}
/// Named Minecraft color
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NamedColor {
Black = 0,
DarkBlue,
DarkGreen,
DarkAqua,
DarkRed,
DarkPurple,
Gold,
Gray,
DarkGray,
Blue,
Green,
Aqua,
Red,
LightPurple,
Yellow,
White,
}

View File

@ -5,6 +5,7 @@ description = "Empowering everyone to host fast and efficient Minecraft servers.
edition = "2021" edition = "2021"
[dependencies] [dependencies]
pumpkin-text = { path = "../pumpkin-text" }
pumpkin-world = { path = "../pumpkin-world"} pumpkin-world = { path = "../pumpkin-world"}
pumpkin-entity = { path = "../pumpkin-entity"} pumpkin-entity = { path = "../pumpkin-entity"}
pumpkin-protocol = { path = "../pumpkin-protocol"} pumpkin-protocol = { path = "../pumpkin-protocol"}

View File

@ -32,9 +32,9 @@ use pumpkin_protocol::{
}, },
status::{SPingRequest, SStatusRequest}, status::{SPingRequest, SStatusRequest},
}, },
text::TextComponent,
ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket, ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket,
}; };
use pumpkin_text::TextComponent;
use std::io::Read; use std::io::Read;
use thiserror::Error; use thiserror::Error;

View File

@ -8,8 +8,8 @@ use pumpkin_protocol::{
SChatCommand, SChatMessage, SConfirmTeleport, SPlayerCommand, SPlayerPosition, SChatCommand, SChatMessage, SConfirmTeleport, SPlayerCommand, SPlayerPosition,
SPlayerPositionRotation, SPlayerRotation, SSwingArm, SPlayerPositionRotation, SPlayerRotation, SSwingArm,
}, },
text::TextComponent,
}; };
use pumpkin_text::TextComponent;
use crate::{ use crate::{
commands::{handle_command, CommandSender}, commands::{handle_command, CommandSender},

View File

@ -1,6 +1,6 @@
use gamemode::GamemodeCommand; use gamemode::GamemodeCommand;
use pumpkin::PumpkinCommand; use pumpkin::PumpkinCommand;
use pumpkin_protocol::text::TextComponent; use pumpkin_text::TextComponent;
use crate::client::Client; use crate::client::Client;

View File

@ -1,4 +1,7 @@
use pumpkin_protocol::{text::TextComponent, CURRENT_MC_PROTOCOL}; use pumpkin_protocol::{
CURRENT_MC_PROTOCOL,
};
use pumpkin_text::{click::ClickEvent, color::NamedColor, hover::HoverEvent, TextComponent};
use crate::server::CURRENT_MC_VERSION; use crate::server::CURRENT_MC_VERSION;
@ -20,92 +23,6 @@ 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})")).color_named(pumpkin_protocol::text::NamedColor::Green)) sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(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),
);
} }
} }