mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
add Block Entities (#737)
* Start implemetnign block entities * fix * Move block entites * bruh wtf * fix * Add seralizers for compund * don't panic on unknown block enity types * Move block entites to pumpkin-world * begin implementation * Chunk packet works * fix signs * fix clippy * merge * Fix signs * merge * fix * merge * fix * remove println * move folder --------- Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
This commit is contained in:
@@ -89,14 +89,14 @@ impl OpenContainer {
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct Chest([Option<ItemStack>; 27]);
|
||||
pub struct ChestContainer([Option<ItemStack>; 27]);
|
||||
|
||||
impl Chest {
|
||||
impl ChestContainer {
|
||||
pub fn new() -> Self {
|
||||
Self([const { None }; 27])
|
||||
}
|
||||
}
|
||||
impl Container for Chest {
|
||||
impl Container for ChestContainer {
|
||||
fn window_type(&self) -> &'static WindowType {
|
||||
&WindowType::Generic9x3
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn block_entity_impl(item: TokenStream) -> TokenStream {
|
||||
let input_string = item.to_string();
|
||||
let block_entity_name = input_string.trim_matches('"');
|
||||
|
||||
quote! {
|
||||
pumpkin_data::block::BLOCK_ENTITY_TYPES
|
||||
.iter()
|
||||
.position(|block_type| *block_type == #block_entity_name)
|
||||
.unwrap() as u32
|
||||
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -343,8 +343,3 @@ mod block_state;
|
||||
pub fn default_block_state(item: TokenStream) -> TokenStream {
|
||||
block_state::default_block_state_impl(item)
|
||||
}
|
||||
mod block;
|
||||
#[proc_macro]
|
||||
pub fn block_entity(item: TokenStream) -> TokenStream {
|
||||
block::block_entity_impl(item)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::deserializer::NbtReadHelper;
|
||||
use crate::serializer::WriteAdaptor;
|
||||
use crate::tag::NbtTag;
|
||||
@@ -241,3 +243,53 @@ impl AsRef<NbtCompound> for NbtCompound {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for NbtCompound {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
let mut map = serializer.serialize_map(Some(self.child_tags.len()))?;
|
||||
for (key, value) in &self.child_tags {
|
||||
map.serialize_entry(key, &value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NbtCompound {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct CompoundVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for CompoundVisitor {
|
||||
type Value = NbtCompound;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("an NBT compound")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut compound = NbtCompound::new();
|
||||
while let Some((key, value)) = map.next_entry::<String, NbtTag>()? {
|
||||
compound.put(&key, value);
|
||||
}
|
||||
Ok(compound)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(CompoundVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NbtCompound> for NbtTag {
|
||||
fn from(value: NbtCompound) -> Self {
|
||||
NbtTag::Compound(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use compound::NbtCompound;
|
||||
use deserializer::NbtReadHelper;
|
||||
use io::Read;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serializer::WriteAdaptor;
|
||||
|
||||
use crate::*;
|
||||
@@ -390,3 +391,128 @@ impl From<bool> for NbtTag {
|
||||
NbtTag::Byte(value as i8)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for NbtTag {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
NbtTag::End => serializer.serialize_unit(),
|
||||
NbtTag::Byte(v) => serializer.serialize_i8(*v),
|
||||
NbtTag::Short(v) => serializer.serialize_i16(*v),
|
||||
NbtTag::Int(v) => serializer.serialize_i32(*v),
|
||||
NbtTag::Long(v) => serializer.serialize_i64(*v),
|
||||
NbtTag::Float(v) => serializer.serialize_f32(*v),
|
||||
NbtTag::Double(v) => serializer.serialize_f64(*v),
|
||||
NbtTag::ByteArray(v) => {
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(v.len()))?;
|
||||
for byte in v.iter() {
|
||||
seq.serialize_element(byte)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
NbtTag::String(v) => serializer.serialize_str(v),
|
||||
NbtTag::List(v) => {
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(v.len()))?;
|
||||
for item in v.iter() {
|
||||
seq.serialize_element(item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
NbtTag::Compound(v) => v.serialize(serializer),
|
||||
NbtTag::IntArray(v) => {
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(v.len()))?;
|
||||
for int in v.iter() {
|
||||
seq.serialize_element(int)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
NbtTag::LongArray(v) => {
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(v.len()))?;
|
||||
for long in v.iter() {
|
||||
seq.serialize_element(long)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NbtTag {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct NbtTagVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for NbtTagVisitor {
|
||||
type Value = NbtTag;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("an NBT tag")
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Byte(v as i8))
|
||||
}
|
||||
|
||||
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Byte(v))
|
||||
}
|
||||
|
||||
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Short(v))
|
||||
}
|
||||
|
||||
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Int(v))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Long(v))
|
||||
}
|
||||
|
||||
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Float(v))
|
||||
}
|
||||
|
||||
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
|
||||
Ok(NbtTag::Double(v))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(NbtTag::String(v.to_string()))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut vec = Vec::new();
|
||||
while let Some(value) = seq.next_element()? {
|
||||
vec.push(value);
|
||||
}
|
||||
Ok(NbtTag::List(vec.into_boxed_slice()))
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
Ok(NbtTag::Compound(NbtCompound::deserialize(
|
||||
serde::de::value::MapAccessDeserializer::new(map),
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(NbtTagVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use crate::{
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT;
|
||||
use pumpkin_macros::packet;
|
||||
use pumpkin_nbt::END_ID;
|
||||
use pumpkin_util::math::position::get_local_cord;
|
||||
use pumpkin_world::chunk::{ChunkData, palette::NetworkPalette};
|
||||
|
||||
#[packet(PLAY_LEVEL_CHUNK_WITH_LIGHT)]
|
||||
@@ -147,8 +149,23 @@ impl ClientPacket for CChunkData<'_> {
|
||||
write.write_slice(&blocks_and_biomes_buf)?;
|
||||
|
||||
// TODO: block entities
|
||||
write.write_var_int(&VarInt(0))?;
|
||||
write.write_var_int(&VarInt(self.0.block_entities.len() as i32))?;
|
||||
for block_entity in self.0.block_entities.values() {
|
||||
let chunk_data_nbt = block_entity.chunk_data_nbt();
|
||||
let pos = block_entity.get_position();
|
||||
let block_entity_id = block_entity.get_id();
|
||||
let local_xz = (get_local_cord(pos.0.x) << 4) | get_local_cord(pos.0.z);
|
||||
write.write_u8_be(local_xz as u8)?;
|
||||
write.write_i16_be(pos.0.y as i16)?;
|
||||
write.write_var_int(&VarInt(block_entity_id as i32))?;
|
||||
if let Some(chunk_data_nbt) = chunk_data_nbt {
|
||||
write.write_nbt(&chunk_data_nbt.into())?;
|
||||
} else {
|
||||
write.write_u8_be(END_ID)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Sky Light Mask
|
||||
// All of the chunks, this is not optimal and uses way more data than needed but will be
|
||||
// overhauled with a full lighting system.
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub mod deserializer;
|
||||
use pumpkin_nbt::{serializer::WriteAdaptor, tag::NbtTag};
|
||||
use thiserror::Error;
|
||||
pub mod packet;
|
||||
pub mod serializer;
|
||||
@@ -322,6 +323,8 @@ pub trait NetworkWriteExt {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_nbt(&mut self, data: &NbtTag) -> Result<(), WritingError>;
|
||||
}
|
||||
|
||||
impl<W: Write> NetworkWriteExt for W {
|
||||
@@ -408,6 +411,40 @@ impl<W: Write> NetworkWriteExt for W {
|
||||
fn write_bitset(&mut self, data: &BitSet) -> Result<(), WritingError> {
|
||||
data.encode(self)
|
||||
}
|
||||
|
||||
fn write_option<G>(
|
||||
&mut self,
|
||||
data: &Option<G>,
|
||||
writer: impl FnOnce(&mut Self, &G) -> Result<(), WritingError>,
|
||||
) -> Result<(), WritingError> {
|
||||
if let Some(data) = data {
|
||||
self.write_bool(true)?;
|
||||
writer(self, data)
|
||||
} else {
|
||||
self.write_bool(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_list<G>(
|
||||
&mut self,
|
||||
list: &[G],
|
||||
writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>,
|
||||
) -> Result<(), WritingError> {
|
||||
self.write_var_int(&(list.len() as i32).into())?;
|
||||
for data in list {
|
||||
writer(self, data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_nbt(&mut self, data: &NbtTag) -> Result<(), WritingError> {
|
||||
let mut write_adaptor = WriteAdaptor::new(self);
|
||||
data.serialize(&mut write_adaptor)
|
||||
.map_err(|e| WritingError::Message(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
34
pumpkin-world/src/block/entities/chest.rs
Normal file
34
pumpkin-world/src/block/entities/chest.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
use super::BlockEntity;
|
||||
|
||||
pub struct ChestBlockEntity {
|
||||
pub position: BlockPos,
|
||||
//pub items: [Item; 27],
|
||||
}
|
||||
|
||||
impl BlockEntity for ChestBlockEntity {
|
||||
fn identifier(&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,
|
||||
{
|
||||
Self { position }
|
||||
}
|
||||
|
||||
fn write_nbt(&self, _nbt: &mut pumpkin_nbt::compound::NbtCompound) {}
|
||||
}
|
||||
|
||||
impl ChestBlockEntity {
|
||||
pub const ID: &'static str = "minecraft:chest";
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self { position }
|
||||
}
|
||||
}
|
||||
53
pumpkin-world/src/block/entities/mod.rs
Normal file
53
pumpkin-world/src/block/entities/mod.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use chest::ChestBlockEntity;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use sign::SignBlockEntity;
|
||||
|
||||
pub mod chest;
|
||||
pub mod sign;
|
||||
|
||||
pub trait BlockEntity: Send + Sync {
|
||||
fn write_nbt(&self, nbt: &mut NbtCompound);
|
||||
fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn identifier(&self) -> &'static str;
|
||||
fn get_position(&self) -> BlockPos;
|
||||
fn write_internal(&self, nbt: &mut NbtCompound) {
|
||||
nbt.put_string("id", self.identifier().to_string());
|
||||
let position = self.get_position();
|
||||
nbt.put_int("x", position.0.x);
|
||||
nbt.put_int("y", position.0.y);
|
||||
nbt.put_int("z", position.0.z);
|
||||
self.write_nbt(nbt);
|
||||
}
|
||||
fn get_id(&self) -> u32 {
|
||||
pumpkin_data::block::BLOCK_ENTITY_TYPES
|
||||
.iter()
|
||||
.position(|block_entity_name| {
|
||||
*block_entity_name == self.identifier().split(":").last().unwrap()
|
||||
})
|
||||
.unwrap() as u32
|
||||
}
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_entity_from_generic<T: BlockEntity>(nbt: &NbtCompound) -> T {
|
||||
let x = nbt.get_int("x").unwrap();
|
||||
let y = nbt.get_int("y").unwrap();
|
||||
let z = nbt.get_int("z").unwrap();
|
||||
T::from_nbt(nbt, BlockPos::new(x, y, z))
|
||||
}
|
||||
|
||||
pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>> {
|
||||
let id = nbt.get_string("id").unwrap();
|
||||
match id.as_str() {
|
||||
ChestBlockEntity::ID => Some(Arc::new(block_entity_from_generic::<ChestBlockEntity>(nbt))),
|
||||
SignBlockEntity::ID => Some(Arc::new(block_entity_from_generic::<SignBlockEntity>(nbt))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
207
pumpkin-world/src/block/entities/sign.rs
Normal file
207
pumpkin-world/src/block/entities/sign.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use super::BlockEntity;
|
||||
use num_derive::FromPrimitive;
|
||||
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
#[derive(Clone, Default, FromPrimitive)]
|
||||
#[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<String> for DyeColor {
|
||||
fn from(s: String) -> Self {
|
||||
match s.as_str() {
|
||||
"white" => DyeColor::White,
|
||||
"orange" => DyeColor::Orange,
|
||||
"magenta" => DyeColor::Magenta,
|
||||
"light_blue" => DyeColor::LightBlue,
|
||||
"yellow" => DyeColor::Yellow,
|
||||
"lime" => DyeColor::Lime,
|
||||
"pink" => DyeColor::Pink,
|
||||
"gray" => DyeColor::Gray,
|
||||
"light_gray" => DyeColor::LightGray,
|
||||
"cyan" => DyeColor::Cyan,
|
||||
"purple" => DyeColor::Purple,
|
||||
"blue" => DyeColor::Blue,
|
||||
"brown" => DyeColor::Brown,
|
||||
"green" => DyeColor::Green,
|
||||
"red" => DyeColor::Red,
|
||||
"black" => DyeColor::Black,
|
||||
_ => DyeColor::Black,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DyeColor> for NbtTag {
|
||||
fn from(value: DyeColor) -> Self {
|
||||
NbtTag::Byte(value as i8)
|
||||
}
|
||||
}
|
||||
|
||||
// NBT data structure
|
||||
pub struct SignBlockEntity {
|
||||
front_text: Text,
|
||||
back_text: Text,
|
||||
is_waxed: bool,
|
||||
position: BlockPos,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct Text {
|
||||
has_glowing_text: bool,
|
||||
color: DyeColor,
|
||||
messages: [String; 4],
|
||||
}
|
||||
|
||||
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);
|
||||
nbt.put_string("color", value.color.into());
|
||||
nbt.put_list(
|
||||
"messages",
|
||||
value.messages.into_iter().map(NbtTag::String).collect(),
|
||||
);
|
||||
NbtTag::Compound(nbt)
|
||||
}
|
||||
}
|
||||
|
||||
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<String> = nbt
|
||||
.get_list("messages")
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|tag| tag.extract_string().cloned())
|
||||
.collect();
|
||||
Self {
|
||||
has_glowing_text,
|
||||
color: DyeColor::from(color.clone()),
|
||||
messages: [
|
||||
// its important that we use unwrap_or since otherwise we may crash on older versions
|
||||
messages.first().unwrap_or(&"".to_string()).clone(),
|
||||
messages.get(1).unwrap_or(&"".to_string()).clone(),
|
||||
messages.get(2).unwrap_or(&"".to_string()).clone(),
|
||||
messages.get(3).unwrap_or(&"".to_string()).clone(),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
fn new(messages: [String; 4]) -> Self {
|
||||
Self {
|
||||
has_glowing_text: false,
|
||||
color: DyeColor::Black,
|
||||
messages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockEntity for SignBlockEntity {
|
||||
fn identifier(&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,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
|
||||
nbt.put("front_text", self.front_text.clone());
|
||||
nbt.put("back_text", self.back_text.clone());
|
||||
nbt.put_bool("is_waxed", self.is_waxed);
|
||||
}
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
self.write_nbt(&mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
}
|
||||
|
||||
impl SignBlockEntity {
|
||||
pub const ID: &'static str = "minecraft:sign";
|
||||
pub fn new(position: BlockPos, is_front: bool, messages: [String; 4]) -> Self {
|
||||
Self {
|
||||
position,
|
||||
is_waxed: false,
|
||||
front_text: if is_front {
|
||||
Text::new(messages.clone())
|
||||
} else {
|
||||
Text::default()
|
||||
},
|
||||
back_text: if !is_front {
|
||||
Text::new(messages.clone())
|
||||
} else {
|
||||
Text::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn empty(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
is_waxed: false,
|
||||
front_text: Text::default(),
|
||||
back_text: Text::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod sign;
|
||||
@@ -1,69 +0,0 @@
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// NBT data structure
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Sign {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
front_text: Option<Text>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
back_text: Option<Text>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
is_waxed: Option<u8>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Text {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
has_glowing_text: Option<u8>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
color: Option<String>,
|
||||
messages: Vec<String>,
|
||||
// TODO: uncomment when pumpkin-nbt supports arrays
|
||||
// messages: [String; 4],
|
||||
}
|
||||
|
||||
impl Text {
|
||||
fn new(messages: [String; 4]) -> Self {
|
||||
Self {
|
||||
has_glowing_text: None,
|
||||
color: None,
|
||||
messages: messages.to_vec(),
|
||||
// TODO: uncomment when pumpkin-nbt supports arrays
|
||||
// messages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sign {
|
||||
pub fn new(location: BlockPos, is_front: bool, messages: [String; 4]) -> Self {
|
||||
let formatted_messages = [
|
||||
format!("\"{}\"", messages[0]),
|
||||
format!("\"{}\"", messages[1]),
|
||||
format!("\"{}\"", messages[2]),
|
||||
format!("\"{}\"", messages[3]),
|
||||
];
|
||||
|
||||
Self {
|
||||
id: "minecraft:sign".to_string(),
|
||||
is_waxed: None,
|
||||
x: location.0.x,
|
||||
y: location.0.y,
|
||||
z: location.0.z,
|
||||
front_text: if is_front {
|
||||
Some(Text::new(formatted_messages.clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
back_text: if !is_front {
|
||||
Some(Text::new(formatted_messages.clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod interactive;
|
||||
pub mod entities;
|
||||
pub mod state;
|
||||
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
@@ -4,7 +4,7 @@ use flate2::read::{GzDecoder, GzEncoder, ZlibDecoder, ZlibEncoder};
|
||||
use itertools::Itertools;
|
||||
use pumpkin_config::advanced_config;
|
||||
use pumpkin_data::{block::Block, chunk::ChunkStatus};
|
||||
use pumpkin_nbt::serializer::to_bytes;
|
||||
use pumpkin_nbt::{compound::NbtCompound, serializer::to_bytes};
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
@@ -874,6 +874,15 @@ pub fn chunk_to_bytes(chunk_data: &ChunkData) -> Result<Vec<u8>, ChunkSerializin
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
block_entities: chunk_data
|
||||
.block_entities
|
||||
.values()
|
||||
.map(|block_entity| {
|
||||
let mut nbt = NbtCompound::new();
|
||||
block_entity.write_internal(&mut nbt);
|
||||
nbt
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pumpkin_data::{block::Block, chunk::ChunkStatus};
|
||||
use pumpkin_nbt::{from_bytes, nbt_long_array};
|
||||
use pumpkin_nbt::{compound::NbtCompound, from_bytes, nbt_long_array};
|
||||
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::generation::section_coords;
|
||||
use crate::{block::entities::block_entity_from_nbt, generation::section_coords};
|
||||
|
||||
use super::{
|
||||
ChunkData, ChunkHeightmaps, ChunkParsingError, ChunkSections, ScheduledTick, SubChunk,
|
||||
@@ -95,6 +95,16 @@ impl ChunkData {
|
||||
.id,
|
||||
})
|
||||
.collect(),
|
||||
block_entities: {
|
||||
let mut block_entities = HashMap::new();
|
||||
for nbt in chunk_data.block_entities {
|
||||
let block_entity = block_entity_from_nbt(&nbt);
|
||||
if let Some(block_entity) = block_entity {
|
||||
block_entities.insert(block_entity.get_position(), block_entity);
|
||||
}
|
||||
}
|
||||
block_entities
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -183,4 +193,6 @@ struct ChunkNbt {
|
||||
block_ticks: Vec<SerializedScheduledTick>,
|
||||
#[serde(rename = "fluid_ticks")]
|
||||
fluid_ticks: Vec<SerializedScheduledTick>,
|
||||
#[serde(rename = "block_entities")]
|
||||
block_entities: Vec<NbtCompound>,
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ where
|
||||
|
||||
//TODO: we need to handle the errors and return the result
|
||||
// files to save
|
||||
let _: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
|
||||
let _test: Vec<Result<(), ChunkWritingError>> = join_all(tasks).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ use palette::{BiomePalette, BlockPalette};
|
||||
use pumpkin_nbt::nbt_long_array;
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::block::entities::BlockEntity;
|
||||
|
||||
use crate::BlockStateId;
|
||||
|
||||
pub mod format;
|
||||
@@ -112,6 +115,7 @@ pub struct ChunkData {
|
||||
pub position: Vector2<i32>,
|
||||
pub block_ticks: Vec<ScheduledTick>,
|
||||
pub fluid_ticks: Vec<ScheduledTick>,
|
||||
pub block_entities: HashMap<BlockPos, Arc<dyn BlockEntity>>,
|
||||
|
||||
pub dirty: bool,
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ impl WorldGenerator for VanillaGenerator {
|
||||
dirty: true,
|
||||
block_ticks: Default::default(),
|
||||
fluid_ticks: Default::default(),
|
||||
block_entities: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use pumpkin_util::math::java_string_hash;
|
||||
use pumpkin_util::random::{RandomImpl, get_seed, legacy_rand::LegacyRand};
|
||||
use pumpkin_util::{
|
||||
math::java_string_hash,
|
||||
random::{RandomImpl, get_seed, legacy_rand::LegacyRand},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Seed(pub u64);
|
||||
|
||||
@@ -54,7 +54,7 @@ pub struct Level {
|
||||
|
||||
// Chunks that are paired with chunk watchers. When a chunk is no longer watched, it is removed
|
||||
// from the loaded chunks map and sent to the underlying ChunkIO
|
||||
pub loaded_chunks: Arc<DashMap<Vector2<i32>, SyncChunk>>,
|
||||
loaded_chunks: Arc<DashMap<Vector2<i32>, SyncChunk>>,
|
||||
chunk_watchers: Arc<DashMap<Vector2<i32>, usize>>,
|
||||
|
||||
chunk_saver: Arc<dyn ChunkIO<Data = SyncChunk>>,
|
||||
|
||||
@@ -7,10 +7,11 @@ use pumpkin_data::{
|
||||
screen::WindowType,
|
||||
sound::{Sound, SoundCategory},
|
||||
};
|
||||
use pumpkin_inventory::{Chest, OpenContainer};
|
||||
use pumpkin_inventory::{ChestContainer, OpenContainer};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_protocol::{client::play::CBlockAction, codec::var_int::VarInt};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::block::entities::chest::ChestBlockEntity;
|
||||
|
||||
use crate::world::World;
|
||||
use crate::{
|
||||
@@ -42,6 +43,30 @@ impl PumpkinBlock for ChestBlock {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn placed(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
_block: &Block,
|
||||
_state_id: u16,
|
||||
pos: &BlockPos,
|
||||
_old_state_id: u16,
|
||||
_notify: bool,
|
||||
) {
|
||||
let chest = ChestBlockEntity::new(*pos);
|
||||
world.add_block_entity(Arc::new(chest)).await;
|
||||
}
|
||||
|
||||
async fn on_state_replaced(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
_block: &Block,
|
||||
location: BlockPos,
|
||||
_old_state_id: u16,
|
||||
_moved: bool,
|
||||
) {
|
||||
world.remove_block_entity(&location).await;
|
||||
}
|
||||
|
||||
async fn use_with_item(
|
||||
&self,
|
||||
block: &Block,
|
||||
@@ -91,7 +116,7 @@ impl ChestBlock {
|
||||
server: &Server,
|
||||
) {
|
||||
// TODO: shouldn't Chest and window type be constrained together to avoid errors?
|
||||
super::standard_open_container::<Chest>(
|
||||
super::standard_open_container::<ChestContainer>(
|
||||
block,
|
||||
player,
|
||||
location,
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(crate) mod furnace;
|
||||
pub(crate) mod jukebox;
|
||||
pub(crate) mod logs;
|
||||
pub(crate) mod redstone;
|
||||
pub(crate) mod signs;
|
||||
pub(crate) mod sugar_cane;
|
||||
pub(crate) mod tnt;
|
||||
pub(crate) mod torches;
|
||||
|
||||
98
pumpkin/src/block/blocks/signs.rs
Normal file
98
pumpkin/src/block/blocks/signs.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::block::Block;
|
||||
use pumpkin_data::block::BlockProperties;
|
||||
use pumpkin_data::block::HorizontalFacing;
|
||||
use pumpkin_data::tag::RegistryKey;
|
||||
use pumpkin_data::tag::get_tag_values;
|
||||
use pumpkin_protocol::server::play::SUseItemOn;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::block::BlockDirection;
|
||||
use pumpkin_world::block::entities::sign::SignBlockEntity;
|
||||
|
||||
use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock};
|
||||
use crate::block::registry::BlockRegistry;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
|
||||
type SignProperties = pumpkin_data::block::OakSignLikeProperties;
|
||||
|
||||
pub fn register_sign_blocks(manager: &mut BlockRegistry) {
|
||||
let tag_values: &'static [&'static str] =
|
||||
get_tag_values(RegistryKey::Block, "minecraft:signs").unwrap();
|
||||
|
||||
for block in tag_values {
|
||||
pub struct SignBlock {
|
||||
id: &'static str,
|
||||
}
|
||||
impl BlockMetadata for SignBlock {
|
||||
fn namespace(&self) -> &'static str {
|
||||
"minecraft"
|
||||
}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PumpkinBlock for SignBlock {
|
||||
async fn on_place(
|
||||
&self,
|
||||
_server: &Server,
|
||||
_world: &World,
|
||||
block: &Block,
|
||||
_face: &BlockDirection,
|
||||
_block_pos: &BlockPos,
|
||||
_use_item_on: &SUseItemOn,
|
||||
_player_direction: &HorizontalFacing,
|
||||
_other: bool,
|
||||
) -> u16 {
|
||||
let sign_props = SignProperties::default(block);
|
||||
|
||||
sign_props.to_state_id(block)
|
||||
}
|
||||
|
||||
async fn placed(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
_block: &Block,
|
||||
_state_id: u16,
|
||||
pos: &BlockPos,
|
||||
_old_state_id: u16,
|
||||
_notify: bool,
|
||||
) {
|
||||
world
|
||||
.add_block_entity(Arc::new(SignBlockEntity::empty(*pos)))
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn player_placed(
|
||||
&self,
|
||||
_world: &Arc<World>,
|
||||
_block: &Block,
|
||||
_state_id: u16,
|
||||
pos: &BlockPos,
|
||||
_face: &BlockDirection,
|
||||
player: &Player,
|
||||
) {
|
||||
player.send_sign_packet(*pos).await;
|
||||
}
|
||||
|
||||
async fn on_state_replaced(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
_block: &Block,
|
||||
location: BlockPos,
|
||||
_old_state_id: u16,
|
||||
_moved: bool,
|
||||
) {
|
||||
world.remove_block_entity(&location).await;
|
||||
}
|
||||
}
|
||||
|
||||
manager.register(SignBlock { id: block });
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use blocks::redstone::redstone_torch::register_redstone_torch_blocks;
|
||||
use blocks::redstone::redstone_wire::RedstoneWireBlock;
|
||||
use blocks::redstone::repeater::RepeaterBlock;
|
||||
use blocks::redstone::target_block::TargetBlock;
|
||||
use blocks::signs::register_sign_blocks;
|
||||
use blocks::sugar_cane::SugarCaneBlock;
|
||||
use blocks::torches::register_torch_blocks;
|
||||
use blocks::{
|
||||
@@ -75,6 +76,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
||||
register_button_blocks(&mut manager);
|
||||
register_torch_blocks(&mut manager);
|
||||
register_redstone_torch_blocks(&mut manager);
|
||||
register_sign_blocks(&mut manager);
|
||||
|
||||
Arc::new(manager)
|
||||
}
|
||||
|
||||
@@ -81,6 +81,17 @@ pub trait PumpkinBlock: Send + Sync {
|
||||
) {
|
||||
}
|
||||
|
||||
async fn player_placed(
|
||||
&self,
|
||||
_world: &Arc<World>,
|
||||
_block: &Block,
|
||||
_state_id: u16,
|
||||
_pos: &BlockPos,
|
||||
_face: &BlockDirection,
|
||||
_player: &Player,
|
||||
) {
|
||||
}
|
||||
|
||||
async fn broken(
|
||||
&self,
|
||||
_block: &Block,
|
||||
|
||||
@@ -126,6 +126,23 @@ impl BlockRegistry {
|
||||
block.default_state_id
|
||||
}
|
||||
|
||||
pub async fn player_placed(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
block: &Block,
|
||||
state_id: u16,
|
||||
pos: &BlockPos,
|
||||
face: &BlockDirection,
|
||||
player: &Player,
|
||||
) {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
pumpkin_block
|
||||
.player_placed(world, block, state_id, pos, face, player)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn can_place_at(&self, world: &World, block: &Block, block_pos: &BlockPos) -> bool {
|
||||
let pumpkin_block = self.get_pumpkin_block(block);
|
||||
if let Some(pumpkin_block) = pumpkin_block {
|
||||
|
||||
@@ -33,10 +33,10 @@ use pumpkin_inventory::InventoryError;
|
||||
use pumpkin_inventory::player::{
|
||||
PlayerInventory, SLOT_HOTBAR_END, SLOT_HOTBAR_START, SLOT_OFFHAND,
|
||||
};
|
||||
use pumpkin_macros::{block_entity, send_cancellable};
|
||||
use pumpkin_macros::send_cancellable;
|
||||
use pumpkin_protocol::client::play::{
|
||||
CBlockEntityData, CBlockUpdate, COpenSignEditor, CPlayerInfoUpdate, CPlayerPosition,
|
||||
CSetContainerSlot, CSetHeldItem, CSystemChatMessage, EquipmentSlot, InitChat, PlayerAction,
|
||||
CBlockUpdate, COpenSignEditor, CPlayerInfoUpdate, CPlayerPosition, CSetContainerSlot,
|
||||
CSetHeldItem, CSystemChatMessage, EquipmentSlot, InitChat, PlayerAction,
|
||||
};
|
||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
@@ -66,7 +66,7 @@ use pumpkin_util::{
|
||||
text::TextComponent,
|
||||
};
|
||||
use pumpkin_world::block::BlockDirection;
|
||||
use pumpkin_world::block::interactive::sign::Sign;
|
||||
use pumpkin_world::block::entities::sign::SignBlockEntity;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
|
||||
use thiserror::Error;
|
||||
@@ -1456,7 +1456,7 @@ impl Player {
|
||||
|
||||
pub async fn handle_sign_update(&self, sign_data: SUpdateSign) {
|
||||
let world = &self.living_entity.entity.world.read().await;
|
||||
let updated_sign = Sign::new(
|
||||
let updated_sign = SignBlockEntity::new(
|
||||
sign_data.location,
|
||||
sign_data.is_front_text,
|
||||
[
|
||||
@@ -1467,15 +1467,7 @@ impl Player {
|
||||
],
|
||||
);
|
||||
|
||||
let mut sign_buf = Vec::new();
|
||||
pumpkin_nbt::serializer::to_bytes_unnamed(&updated_sign, &mut sign_buf).unwrap();
|
||||
world
|
||||
.broadcast_packet_all(&CBlockEntityData::new(
|
||||
sign_data.location,
|
||||
VarInt(block_entity!("sign") as i32),
|
||||
sign_buf.into_boxed_slice(),
|
||||
))
|
||||
.await;
|
||||
world.add_block_entity(Arc::new(updated_sign)).await;
|
||||
}
|
||||
|
||||
pub async fn handle_use_item(&self, _use_item: &SUseItem, server: &Server) {
|
||||
@@ -1750,7 +1742,11 @@ impl Player {
|
||||
.set_block_state(&final_block_pos, new_state, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
|
||||
self.send_sign_packet(block, final_block_pos, face).await;
|
||||
server
|
||||
.block_registry
|
||||
.player_placed(world, &block, new_state, &final_block_pos, face, self)
|
||||
.await;
|
||||
|
||||
// The block was placed successfully, so decrement their inventory
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -1759,22 +1755,9 @@ impl Player {
|
||||
}
|
||||
|
||||
/// Checks if the block placed was a sign, then opens a dialog.
|
||||
async fn send_sign_packet(
|
||||
&self,
|
||||
block: Block,
|
||||
block_position: BlockPos,
|
||||
selected_face: &BlockDirection,
|
||||
) {
|
||||
if block.states.iter().any(|state| {
|
||||
state.get_state().block_entity_type == Some(block_entity!("sign"))
|
||||
|| state.get_state().block_entity_type == Some(block_entity!("hanging_sign"))
|
||||
}) {
|
||||
self.client
|
||||
.enqueue_packet(&COpenSignEditor::new(
|
||||
block_position,
|
||||
selected_face.to_offset().z == 1,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
pub async fn send_sign_packet(&self, block_position: BlockPos) {
|
||||
self.client
|
||||
.enqueue_packet(&COpenSignEditor::new(block_position, true))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,11 @@ use pumpkin_data::{
|
||||
world::{RAW, WorldEvent},
|
||||
};
|
||||
use pumpkin_macros::send_cancellable;
|
||||
use pumpkin_nbt::to_bytes_unnamed;
|
||||
use pumpkin_protocol::{
|
||||
ClientPacket, IdOr, SoundEvent,
|
||||
client::play::{
|
||||
CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate, CPlayerChatMessage,
|
||||
CBlockEntityData, CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate, CPlayerChatMessage,
|
||||
CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CSoundEffect, CSpawnEntity,
|
||||
FilterType, GameEvent, InitChat, PlayerAction, PlayerInfoFlags,
|
||||
},
|
||||
@@ -55,7 +56,10 @@ use pumpkin_registry::DimensionType;
|
||||
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
|
||||
use pumpkin_util::math::{position::chunk_section_from_pos, vector2::Vector2};
|
||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
||||
use pumpkin_world::{BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, level::SyncChunk};
|
||||
use pumpkin_world::{
|
||||
BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity,
|
||||
level::SyncChunk,
|
||||
};
|
||||
use pumpkin_world::{block::BlockDirection, chunk::ChunkData};
|
||||
use pumpkin_world::{chunk::TickPriority, level::Level};
|
||||
use rand::{Rng, thread_rng};
|
||||
@@ -1646,4 +1650,39 @@ impl World {
|
||||
self.set_block_state(block_pos, new_state_id, flags).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_block_entity(&self, block_pos: &BlockPos) -> Option<Arc<dyn BlockEntity>> {
|
||||
let chunk = self.get_chunk(block_pos).await;
|
||||
let chunk: tokio::sync::RwLockReadGuard<ChunkData> = chunk.read().await;
|
||||
|
||||
chunk.block_entities.get(block_pos).cloned()
|
||||
}
|
||||
|
||||
pub async fn add_block_entity(&self, block_entity: Arc<dyn BlockEntity>) {
|
||||
let block_pos = block_entity.get_position();
|
||||
let chunk = self.get_chunk(&block_pos).await;
|
||||
let mut chunk: tokio::sync::RwLockWriteGuard<ChunkData> = chunk.write().await;
|
||||
let block_entity_nbt = block_entity.chunk_data_nbt();
|
||||
|
||||
if let Some(nbt) = block_entity_nbt {
|
||||
let mut bytes = Vec::new();
|
||||
to_bytes_unnamed(&nbt, &mut bytes).unwrap();
|
||||
self.broadcast_packet_all(&CBlockEntityData::new(
|
||||
block_entity.get_position(),
|
||||
VarInt(block_entity.get_id() as i32),
|
||||
bytes.into_boxed_slice(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
chunk.block_entities.insert(block_pos, block_entity);
|
||||
chunk.dirty = true;
|
||||
}
|
||||
|
||||
pub async fn remove_block_entity(&self, block_pos: &BlockPos) {
|
||||
let chunk = self.get_chunk(block_pos).await;
|
||||
let mut chunk: tokio::sync::RwLockWriteGuard<ChunkData> = chunk.write().await;
|
||||
chunk.block_entities.remove(block_pos);
|
||||
chunk.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user