Pumpkin/pumpkin/src/block/entities/sign.rs
Alexander Medvedev 3303bb7b97 chore(net): change get_string to get_str
changed return value from `String` to `Box<str>` which is smaller and makes more sense since we don't mutate packet data

String — 24 bytes (ptr + len + capacity)
Box<str> — 16 bytes (ptr + len)
2026-05-23 11:21:53 +02:00

296 lines
8.2 KiB
Rust

use std::{
pin::Pin,
sync::{
Arc,
atomic::{AtomicBool, AtomicI8, Ordering},
},
};
use super::BlockEntity;
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
#[repr(i8)]
pub enum DyeColor {
White = 0,
Orange = 1,
Magenta = 2,
LightBlue = 3,
Yellow = 4,
Lime = 5,
Pink = 6,
Gray = 7,
LightGray = 8,
Cyan = 9,
Purple = 10,
Blue = 11,
Brown = 12,
Green = 13,
Red = 14,
#[default]
Black = 15,
}
impl From<DyeColor> for String {
fn from(value: DyeColor) -> Self {
match value {
DyeColor::White => "white".to_string(),
DyeColor::Orange => "orange".to_string(),
DyeColor::Magenta => "magenta".to_string(),
DyeColor::LightBlue => "light_blue".to_string(),
DyeColor::Yellow => "yellow".to_string(),
DyeColor::Lime => "lime".to_string(),
DyeColor::Pink => "pink".to_string(),
DyeColor::Gray => "gray".to_string(),
DyeColor::LightGray => "light_gray".to_string(),
DyeColor::Cyan => "cyan".to_string(),
DyeColor::Purple => "purple".to_string(),
DyeColor::Blue => "blue".to_string(),
DyeColor::Brown => "brown".to_string(),
DyeColor::Green => "green".to_string(),
DyeColor::Red => "red".to_string(),
DyeColor::Black => "black".to_string(),
}
}
}
impl From<&str> for DyeColor {
fn from(s: &str) -> Self {
match s {
"white" => Self::White,
"orange" => Self::Orange,
"magenta" => Self::Magenta,
"light_blue" => Self::LightBlue,
"yellow" => Self::Yellow,
"lime" => Self::Lime,
"pink" => Self::Pink,
"gray" => Self::Gray,
"light_gray" => Self::LightGray,
"cyan" => Self::Cyan,
"purple" => Self::Purple,
"blue" => Self::Blue,
"brown" => Self::Brown,
"green" => Self::Green,
"red" => Self::Red,
"black" => Self::Black,
_ => Self::default(),
}
}
}
impl From<i8> for DyeColor {
fn from(s: i8) -> Self {
match s {
0 => Self::White,
1 => Self::Orange,
2 => Self::Magenta,
3 => Self::LightBlue,
4 => Self::Yellow,
5 => Self::Lime,
6 => Self::Pink,
7 => Self::Gray,
8 => Self::LightGray,
9 => Self::Cyan,
10 => Self::Purple,
11 => Self::Blue,
12 => Self::Brown,
13 => Self::Green,
14 => Self::Red,
15 => Self::Black,
_ => Self::default(),
}
}
}
impl From<DyeColor> for NbtTag {
fn from(value: DyeColor) -> Self {
Self::Byte(value as i8)
}
}
pub struct SignBlockEntity {
pub front_text: Text,
pub back_text: Text,
pub is_waxed: AtomicBool,
position: BlockPos,
pub currently_editing_player: Arc<Mutex<Option<uuid::Uuid>>>,
}
pub struct Text {
pub has_glowing_text: AtomicBool,
color: AtomicI8,
pub messages: Arc<std::sync::Mutex<[Box<str>; 4]>>,
}
impl Clone for Text {
fn clone(&self) -> Self {
Self {
has_glowing_text: AtomicBool::new(self.has_glowing_text.load(Ordering::Relaxed)),
color: AtomicI8::new(self.color.load(Ordering::Relaxed)),
messages: self.messages.clone(),
}
}
}
impl Default for Text {
fn default() -> Self {
Self {
has_glowing_text: AtomicBool::new(false),
color: AtomicI8::new(DyeColor::default() as i8),
messages: Arc::default(),
}
}
}
#[allow(clippy::fallible_impl_from)]
impl From<Text> for NbtTag {
fn from(value: Text) -> Self {
let mut nbt = NbtCompound::new();
nbt.put_bool(
"has_glowing_text",
value.has_glowing_text.load(Ordering::Relaxed),
);
nbt.put_string("color", value.get_color().into());
nbt.put_list(
"messages",
value
.messages
.lock()
.unwrap()
.iter()
.map(|s| Self::String(s.clone()))
.collect(),
);
Self::Compound(nbt)
}
}
#[allow(clippy::fallible_impl_from)]
impl From<NbtTag> for Text {
fn from(tag: NbtTag) -> Self {
let nbt = tag.extract_compound().unwrap();
let has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false);
let color = nbt.get_string("color").unwrap();
let messages: Vec<Box<str>> = nbt
.get_list("messages")
.unwrap()
.iter()
.filter_map(|tag| tag.extract_string().map(Box::from))
.collect();
let get_message =
|i: usize| -> Box<str> { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) };
Self {
has_glowing_text: AtomicBool::new(has_glowing_text),
color: AtomicI8::new(DyeColor::from(color) as i8),
messages: Arc::new(std::sync::Mutex::new([
get_message(0),
get_message(1),
get_message(2),
get_message(3),
])),
}
}
}
impl Text {
fn new(messages: [Box<str>; 4]) -> Self {
Self {
has_glowing_text: AtomicBool::new(false),
color: AtomicI8::new(DyeColor::default() as i8),
messages: Arc::new(std::sync::Mutex::new(messages)),
}
}
pub fn get_color(&self) -> DyeColor {
self.color.load(Ordering::Relaxed).into()
}
pub fn set_color(&self, color: DyeColor) {
self.color.store(color as i8, Ordering::Relaxed);
}
}
impl BlockEntity for SignBlockEntity {
fn resource_location(&self) -> &'static str {
Self::ID
}
fn get_position(&self) -> BlockPos {
self.position
}
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
where
Self: Sized,
{
let front_text = Text::from(nbt.get("front_text").unwrap().clone());
let back_text = Text::from(nbt.get("back_text").unwrap().clone());
let is_waxed = nbt.get_bool("is_waxed").unwrap_or(false);
Self {
position,
front_text,
back_text,
is_waxed: AtomicBool::new(is_waxed),
currently_editing_player: Arc::new(Mutex::new(None)),
}
}
fn write_nbt<'a>(
&'a self,
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
nbt.put("front_text", self.front_text.clone());
nbt.put("back_text", self.back_text.clone());
nbt.put_bool("is_waxed", self.is_waxed.load(Ordering::Relaxed));
})
}
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
let mut nbt = NbtCompound::new();
nbt.put("front_text", self.front_text.clone());
nbt.put("back_text", self.back_text.clone());
nbt.put_bool("is_waxed", self.is_waxed.load(Ordering::Relaxed));
Some(nbt)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl SignBlockEntity {
pub const ID: &'static str = "minecraft:sign";
#[must_use]
pub fn new(position: BlockPos, is_front: bool, messages: [Box<str>; 4]) -> Self {
Self {
position,
is_waxed: AtomicBool::new(false),
front_text: if is_front {
Text::new(messages.clone())
} else {
Text::default()
},
back_text: if is_front {
Text::default()
} else {
Text::new(messages)
},
currently_editing_player: Arc::new(Mutex::new(None)),
}
}
#[must_use]
pub fn empty(position: BlockPos) -> Self {
Self {
position,
is_waxed: AtomicBool::new(false),
front_text: Text::default(),
back_text: Text::default(),
currently_editing_player: Arc::new(Mutex::new(None)),
}
}
}