From 19458f33cc8d8dfcfc13423297f187950b5533c1 Mon Sep 17 00:00:00 2001 From: Leobeg <147538729+leobeg@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:06:34 +0100 Subject: [PATCH] Added foundation --- pumpkin-core/src/math/position.rs | 6 +- pumpkin-world/src/block/block_entity.rs | 47 ++++++++++++ pumpkin-world/src/block/mod.rs | 1 + pumpkin-world/src/chunk/mod.rs | 14 +++- pumpkin-world/src/item/item_registry.rs | 17 +++++ .../src/world_gen/generic_generator.rs | 2 + .../src/world_gen/implementation/test.rs | 3 +- pumpkin/src/block/blocks/jukebox.rs | 74 ++++++++++++++++++- pumpkin/src/client/player_packet.rs | 27 +++++-- pumpkin/src/world/mod.rs | 34 +++++++++ 10 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 pumpkin-world/src/block/block_entity.rs diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index 839890646..6144edd5e 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -5,7 +5,7 @@ use crate::math::vector2::Vector2; use num_traits::Euclid; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] /// Aka Block Position pub struct WorldPosition(pub Vector3); @@ -27,6 +27,10 @@ impl WorldPosition { }; (chunk_coordinate, relative) } + + pub fn from_xyz(x: i32, y: i32, z: i32) -> Self { + Self(Vector3::new(x, y, z)) + } } impl Serialize for WorldPosition { fn serialize(&self, serializer: S) -> Result diff --git a/pumpkin-world/src/block/block_entity.rs b/pumpkin-world/src/block/block_entity.rs new file mode 100644 index 000000000..1dd1a4f01 --- /dev/null +++ b/pumpkin-world/src/block/block_entity.rs @@ -0,0 +1,47 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct BlockEntityItem { + count: Option, + slot: Option, + #[serde(rename = "id")] + id: String, + //tag - Currently not needed +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BlockEntity { + pub x: i32, + pub y: i32, + pub z: i32, + // #[serde(rename = "id")] + // pub id: String, + #[serde(flatten)] + pub data: BlockEntityType +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(tag = "id")] +pub enum BlockEntityType { + + #[serde(rename = "minecraft:jukebox")] + Jukebox { + #[serde(rename = "RecordItem")] + record_item: Option, + ticks_since_song_started: Option, + }, + + #[serde(other)] + Unknown +} + + +/// --- Entity specific structs --- + +/// Jukebox record item +#[derive(Debug, Clone, Deserialize)] +pub struct RecordItem { + pub count: u8, + pub id: String, +} \ No newline at end of file diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index e5d10db60..086310839 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -2,6 +2,7 @@ use num_derive::FromPrimitive; pub mod block_registry; pub mod block_state; +pub mod block_entity; use pumpkin_core::math::vector3::Vector3; diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 93edbbfaa..9e89d50a8 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -5,13 +5,14 @@ use std::cmp::max; use std::collections::HashMap; use std::ops::Index; use thiserror::Error; - +use pumpkin_core::math::position::WorldPosition; use crate::{ block::BlockState, coordinates::{ChunkRelativeBlockCoordinates, Height}, level::SaveFile, WORLD_HEIGHT, }; +use crate::block::block_entity::BlockEntity; pub mod anvil; @@ -55,6 +56,7 @@ pub enum CompressionError { pub struct ChunkData { pub blocks: ChunkBlocks, + pub block_entities: HashMap, pub position: Vector2, } pub struct ChunkBlocks { @@ -108,6 +110,9 @@ struct ChunkNbt { #[serde(rename = "sections")] sections: Vec, + #[serde(rename = "block_entities")] + block_entities: Vec, + heightmaps: ChunkHeightmaps, } @@ -310,9 +315,16 @@ impl ChunkData { } } + let mut block_entities: HashMap = HashMap::new(); + for block_entity in chunk_data.block_entities { + let position = WorldPosition::from_xyz(block_entity.x, block_entity.y, block_entity.z); + block_entities.insert(position, block_entity); + } + Ok(ChunkData { blocks, position: at, + block_entities, }) } } diff --git a/pumpkin-world/src/item/item_registry.rs b/pumpkin-world/src/item/item_registry.rs index e446e3622..a010a52b8 100644 --- a/pumpkin-world/src/item/item_registry.rs +++ b/pumpkin-world/src/item/item_registry.rs @@ -21,6 +21,23 @@ pub fn get_item_by_id<'a>(id: u16) -> Option<&'a Item> { None } +pub fn get_record_item(name: &str) -> Option<&Item> { + let item = ITEMS.iter().find(|item| { + item.1 + .components + .jukebox_playable + .as_ref() + .map(|playable| playable.song == name.to_string()) + .unwrap_or(false) + }); + + if let Some(item) = item { + return Some(item.1); + } + + None +} + #[derive(Deserialize, Clone, Debug)] pub struct Item { pub id: u16, diff --git a/pumpkin-world/src/world_gen/generic_generator.rs b/pumpkin-world/src/world_gen/generic_generator.rs index de3f02cac..e03388bbb 100644 --- a/pumpkin-world/src/world_gen/generic_generator.rs +++ b/pumpkin-world/src/world_gen/generic_generator.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use noise::{NoiseFn, Perlin}; use pumpkin_core::math::vector2::Vector2; @@ -75,6 +76,7 @@ impl WorldGenerator for GenericGen ChunkData { blocks, position: at, + block_entities: HashMap::new(), } } } diff --git a/pumpkin-world/src/world_gen/implementation/test.rs b/pumpkin-world/src/world_gen/implementation/test.rs index 6b360c45a..611697bcf 100644 --- a/pumpkin-world/src/world_gen/implementation/test.rs +++ b/pumpkin-world/src/world_gen/implementation/test.rs @@ -2,7 +2,7 @@ use std::{ num::Wrapping, ops::{AddAssign, SubAssign}, }; - +use std::collections::HashMap; use dashmap::{DashMap, Entry}; use num_traits::Zero; use pumpkin_core::math::{vector2::Vector2, vector3::Vector3}; @@ -77,6 +77,7 @@ impl WorldGenerator for TestGenerator(&self, player: &Player, location: WorldPosition, _server: &Server) { - // For now just stop the music at this position let world = &player.living_entity.entity.world; - world.stop_record(location).await; + let block_entity = world.get_block_entity(&location).await; + let Some(block_entity) = block_entity else { + return; + }; + + let record_item = match block_entity.data { + BlockEntityType::Jukebox { record_item, .. } => record_item, + _ => return, + }; + + dbg!(&record_item); + + if let Some(record_item) = record_item { + //TODO: The record would drop here for now just give the disk to the player + let item = get_record_item(record_item.id.as_str()); + + let Some(item) = item else { + return; + }; + + player.give_items(item, 1).await; + + world.stop_record(location).await; + + Self::write_block_entity(world, location, None, None).await; + } } async fn on_use_with_item<'a>( @@ -46,13 +73,54 @@ impl PumpkinBlock for JukeboxBlock { world.play_record(jukebox_song as i32, location).await; + Self::write_block_entity( + world, + location, + Some(RecordItem { + count: 1, + id: jukebox_playable.song.clone(), + }), + Some(0), + ) + .await; + BlockActionResult::Consume } + async fn on_placed<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + let world = &player.living_entity.entity.world; + Self::write_block_entity(world, location, None, None).await; + } + async fn on_broken<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { // For now just stop the music at this position let world = &player.living_entity.entity.world; world.stop_record(location).await; + + world.remove_block_entity(location).await; + } +} + +impl JukeboxBlock { + pub async fn write_block_entity( + world: &Arc, + location: WorldPosition, + record_item: Option, + ticks_since_song_started: Option, + ) { + let block_entity = BlockEntity { + x: location.0.x, + y: location.0.y, + z: location.0.z, + data: BlockEntityType::Jukebox { + record_item, + ticks_since_song_started, + }, + }; + + world + .create_or_update_block_entity(location, block_entity) + .await; } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 1dd55bf0f..a4175368a 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -39,6 +39,7 @@ use pumpkin_protocol::{ use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace}; use pumpkin_world::item::item_registry::get_item_by_id; use thiserror::Error; +use pumpkin_protocol::client::play::CBlockUpdate; fn modulus(a: f32, b: f32) -> f32 { ((a % b) + b) % b @@ -652,6 +653,8 @@ impl Player { // TODO: maybe log? return Err(BlockPlacingError::BlockOutOfReach.into()); } + + log::info!("Test"); if let Some(face) = BlockFace::from_i32(use_item_on.face.0) { let inventory = self.inventory().lock().await; @@ -672,6 +675,9 @@ impl Player { match result { BlockActionResult::Continue => {} BlockActionResult::Consume => { + self.client + .send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)) + .await; return Ok(()); } } @@ -687,6 +693,9 @@ impl Player { let item_slot = inventory.held_item_mut(); // This should never be possible let Some(item_stack) = item_slot else { + self.client + .send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)) + .await; return Err(BlockPlacingError::InventoryInvalid.into()); }; item_stack.item_count -= 1; @@ -706,6 +715,9 @@ impl Player { let previous_block_state = world.get_block_state(world_pos).await?; if !previous_block_state.replaceable { + self.client + .send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)) + .await; return Ok(()); } @@ -732,22 +744,27 @@ impl Player { .on_placed(block, self, world_pos, server) .await; } - - self.client - .send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)) - .await; } } else { + self.client + .send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence)) + .await; drop(inventory); let block = world.get_block(location).await; if let Ok(block) = block { server .block_manager - .on_use(block, self, location, server) + .on_use(block, self, location.clone(), server) .await; } + let block_state = world.get_block_state_id(location).await.unwrap(); + self.client + .send_packet(&CBlockUpdate::new(&location, VarInt::from(block_state as u32))) + .await; } + + Ok(()) } else { Err(BlockPlacingError::InvalidBlockFace.into()) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b36f51ce7..d043b6817 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -45,6 +45,7 @@ use tokio::{ runtime::Handle, sync::{mpsc, RwLock}, }; +use pumpkin_world::block::block_entity::BlockEntity; use worldborder::Worldborder; pub mod bossbar; @@ -825,4 +826,37 @@ impl World { let id = self.get_block_state_id(position).await?; get_block_and_state_by_state_id(id).ok_or(GetBlockError::InvalidBlockId) } + + pub async fn get_block_entity(&self, position: &WorldPosition) -> Option { + let chunk_coordinate = position.chunk_and_chunk_relative_position().0; + let chunk = self.receive_chunk(chunk_coordinate).await; + + let block_entity = chunk.read().await.block_entities.get(position).cloned(); + + block_entity + } + + pub async fn create_or_update_block_entity( + &self, + position: WorldPosition, + block_entity: BlockEntity, + ) { + let chunk_coordinate = position.chunk_and_chunk_relative_position().0; + let chunk = self.receive_chunk(chunk_coordinate).await; + + chunk + .write() + .await + .block_entities + .insert(position, block_entity); + + + } + + pub async fn remove_block_entity(&self, position: WorldPosition) { + let chunk_coordinate = position.chunk_and_chunk_relative_position().0; + let chunk = self.receive_chunk(chunk_coordinate).await; + + chunk.write().await.block_entities.remove(&position); + } }