feat: add from_legacy_string to TextComponent

This commit is contained in:
Alexander Medvedev 2026-01-30 23:12:54 +01:00
parent d64f538138
commit 72bd4dfbef
7 changed files with 150 additions and 28 deletions

6
Cargo.lock generated
View File

@ -636,9 +636,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "crossfire" name = "crossfire"
version = "3.0.2" version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e1ff29ef3add47897a4dab381889ccf8dedcedec4e2c8feed37efcf22a6392b" checksum = "cf9c756fd351d7bc1f95698a33a90ce39c8f7458335cc7e95132d3d36124902a"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
"futures-core", "futures-core",
@ -2386,7 +2386,7 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.10.0-rc.8" version = "0.10.0-rc.8"
source = "git+https://github.com/rust-random/rand#47455a0b004d5992d2a3d23ecf244f1edea27f40" source = "git+https://github.com/rust-random/rand#a4c9a8e565fa310de423e3ba89593b647b721e8c"
dependencies = [ dependencies = [
"chacha20", "chacha20",
"getrandom 0.4.0-rc.1", "getrandom 0.4.0-rc.1",

View File

@ -115,7 +115,7 @@ console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = "1.10.0" crc-fast = "1.10.0"
criterion = { version = "0.8", default-features = false } criterion = { version = "0.8", default-features = false }
crossbeam-utils = "0.8.21" crossbeam-utils = "0.8.21"
crossfire = { version= "3.0.2", features = ["compat"]} crossfire = { version= "3.0.3", features = ["compat"]}
dashmap = "6.1" dashmap = "6.1"
ecdsa = "0.16.9" ecdsa = "0.16.9"
enum_dispatch = "0.3.13" enum_dispatch = "0.3.13"

View File

@ -92,6 +92,41 @@ impl Color {
Self::Rgb(color) => text.truecolor(color.red, color.green, color.blue), Self::Rgb(color) => text.truecolor(color.red, color.green, color.blue),
} }
} }
#[must_use]
pub const fn from_legacy_code(code: char) -> Option<Self> {
let named = match code.to_ascii_lowercase() {
'0' => NamedColor::Black,
'1' => NamedColor::DarkBlue,
'2' => NamedColor::DarkGreen,
'3' => NamedColor::DarkAqua,
'4' => NamedColor::DarkRed,
'5' => NamedColor::DarkPurple,
'6' => NamedColor::Gold,
'7' => NamedColor::Gray,
'8' => NamedColor::DarkGray,
'9' => NamedColor::Blue,
'a' => NamedColor::Green,
'b' => NamedColor::Aqua,
'c' => NamedColor::Red,
'd' => NamedColor::LightPurple,
'e' => NamedColor::Yellow,
'f' => NamedColor::White,
_ => return None,
};
Some(Self::Named(named))
}
#[must_use]
pub fn from_hex_str(hex: &str) -> Option<Self> {
if hex.len() != 6 {
return None;
}
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some(Self::Rgb(RGBColor::new(r, g, b)))
}
} }
#[derive(Debug, Deserialize, Clone, Copy, Eq, Hash, PartialEq)] #[derive(Debug, Deserialize, Clone, Copy, Eq, Hash, PartialEq)]

View File

@ -284,6 +284,109 @@ impl TextComponent {
}) })
} }
#[must_use]
pub fn from_legacy_string(input: &str) -> Self {
let mut root = Self::text("");
let parts: Vec<&str> = input.split('§').collect();
if !parts[0].is_empty() {
root = root.add_child(Self::text(parts[0].to_string()));
}
let mut current_color: Option<Color> = None;
let mut bold = false;
let mut italic = false;
let mut underlined = false;
let mut strikethrough = false;
let mut obfuscated = false;
let mut i = 1;
while i < parts.len() {
let part = parts[i];
if part.is_empty() {
i += 1;
continue;
}
let mut chars = part.chars();
let code = chars.next().unwrap_or(' ').to_ascii_lowercase();
let remainder = &part[1..];
match code {
'x' if i + 6 < parts.len() => {
let mut hex = String::new();
for j in 1..=6 {
if let Some(c) = parts[i + j].chars().next() {
hex.push(c);
}
}
current_color = Color::from_hex_str(&hex);
i += 6;
let last_part = parts[i];
if last_part.len() > 1 {
let mut child = Self::text(last_part[1..].to_string());
if let Some(c) = current_color {
child = child.color(c);
}
root = root.add_child(child);
}
i += 1;
continue;
}
'0'..='9' | 'a'..='f' => {
current_color = Color::from_legacy_code(code);
bold = false;
italic = false;
underlined = false;
strikethrough = false;
obfuscated = false;
}
'l' => bold = true,
'o' => italic = true,
'n' => underlined = true,
'm' => strikethrough = true,
'k' => obfuscated = true,
'r' => {
current_color = None;
bold = false;
italic = false;
underlined = false;
strikethrough = false;
obfuscated = false;
}
_ => {}
}
if !remainder.is_empty() {
let mut child = Self::text(remainder.to_string());
if let Some(c) = current_color {
child = child.color(c);
}
if bold {
child = child.bold();
}
if italic {
child = child.italic();
}
if underlined {
child = child.underlined();
}
if strikethrough {
child = child.strikethrough();
}
if obfuscated {
child = child.obfuscated();
}
root = root.add_child(child);
}
i += 1;
}
root
}
#[must_use] #[must_use]
pub fn add_child(mut self, child: Self) -> Self { pub fn add_child(mut self, child: Self) -> Self {
self.0.extra.push(child.0); self.0.extra.push(child.0);

View File

@ -983,7 +983,7 @@ impl GenerationCache for Cache {
// debug_assert!(dx >= 0 && dz >= 0); // debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) { if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here // breakpoint here
log::error!( log::debug!(
"illegal get_block_state {pos:?} cache pos ({}, {}) size {}", "illegal get_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.x,
self.z, self.z,
@ -1011,7 +1011,7 @@ impl GenerationCache for Cache {
// debug_assert!(dx >= 0 && dz >= 0); // debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) { if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here // breakpoint here
log::error!( log::debug!(
"illegal set_block_state {pos:?} cache pos ({}, {}) size {}", "illegal set_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.x,
self.z, self.z,

View File

@ -178,8 +178,6 @@ impl PumpkinServer {
let rcon = server.advanced_config.networking.rcon.clone(); let rcon = server.advanced_config.networking.rcon.clone();
let mut ticker = Ticker::new();
if server.advanced_config.commands.use_console if server.advanced_config.commands.use_console
&& let Some((wrapper, _)) = LOGGER_IMPL.wait() && let Some((wrapper, _)) = LOGGER_IMPL.wait()
{ {
@ -267,7 +265,7 @@ impl PumpkinServer {
{ {
let ticker_server = server.clone(); let ticker_server = server.clone();
server.spawn_task(async move { server.spawn_task(async move {
ticker.run(&ticker_server).await; Ticker::run(&ticker_server).await;
}); });
}; };

View File

@ -5,26 +5,12 @@ use std::{
}; };
use tokio::time::sleep; use tokio::time::sleep;
pub struct Ticker { pub struct Ticker;
last_tick: Instant,
}
impl Default for Ticker {
fn default() -> Self {
Self::new()
}
}
impl Ticker { impl Ticker {
#[must_use]
pub fn new() -> Self {
Self {
last_tick: Instant::now(),
}
}
/// IMPORTANT: Run this in a new thread/tokio task. /// IMPORTANT: Run this in a new thread/tokio task.
pub async fn run(&mut self, server: &Arc<Server>) { pub async fn run(server: &Arc<Server>) {
let mut last_tick = Instant::now();
while !SHOULD_STOP.load(Ordering::Relaxed) { while !SHOULD_STOP.load(Ordering::Relaxed) {
let tick_start_time = Instant::now(); let tick_start_time = Instant::now();
let manager = &server.tick_rate_manager; let manager = &server.tick_rate_manager;
@ -54,7 +40,7 @@ impl Ticker {
// Sleep logic remains the same // Sleep logic remains the same
let now = Instant::now(); let now = Instant::now();
let elapsed = now.duration_since(self.last_tick); let elapsed = now.duration_since(last_tick);
let tick_interval = if manager.is_sprinting() { let tick_interval = if manager.is_sprinting() {
Duration::ZERO Duration::ZERO
@ -68,7 +54,7 @@ impl Ticker {
sleep(sleep_time).await; sleep(sleep_time).await;
} }
self.last_tick = Instant::now(); last_tick = Instant::now();
} }
log::debug!("Ticker stopped"); log::debug!("Ticker stopped");
} }