diff --git a/README.md b/README.md index f1757aaf2..ca5013ed8 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [x] Chunk Loading (Vanilla, Linear) - [x] Chunk Generation - [x] Chunk Saving (Vanilla, Linear) - - [ ] Redstone + - [x] Redstone - [ ] Liquid Physics - [x] Biomes - [ ] Vegetation diff --git a/pumpkin-data/build/block.rs b/pumpkin-data/build/block.rs index 9c1432e0e..50fe84946 100644 --- a/pumpkin-data/build/block.rs +++ b/pumpkin-data/build/block.rs @@ -6,6 +6,19 @@ use serde::Deserialize; use std::collections::{HashMap, HashSet}; use syn::{Ident, LitBool, LitInt, LitStr}; +fn is_state_solid(state: &BlockState, all_shapes: &[CollisionShape]) -> bool { + // Needs min xyz to be 0,0,0 and max xyz to be 1,1,1 + state.collision_shapes.iter().any(|shape| { + let shape = all_shapes.get(*shape as usize).unwrap(); + shape.min[0] == 0.0 + && shape.min[1] == 0.0 + && shape.min[2] == 0.0 + && shape.max[0] == 1.0 + && shape.max[1] == 1.0 + && shape.max[2] == 1.0 + }) +} + fn const_block_name_from_block_name(block: &str) -> String { block.to_shouty_snake_case() } @@ -327,8 +340,9 @@ pub struct BlockStateRef { pub state_idx: u16, } -impl ToTokens for BlockState { - fn to_tokens(&self, tokens: &mut TokenStream) { +impl BlockState { + fn to_tokens(&self, all_shapes: &[CollisionShape]) -> TokenStream { + let mut tokens = TokenStream::new(); //let id = LitInt::new(&self.id.to_string(), Span::call_site()); let air = LitBool::new(self.air, Span::call_site()); let luminance = LitInt::new(&self.luminance.to_string(), Span::call_site()); @@ -359,6 +373,8 @@ impl ToTokens for BlockState { .iter() .map(|shape_id| LitInt::new(&shape_id.to_string(), Span::call_site())); + let is_solid = is_state_solid(self, all_shapes); + tokens.extend(quote! { PartialBlockState { air: #air, @@ -372,8 +388,10 @@ impl ToTokens for BlockState { opacity: #opacity, block_entity_type: #block_entity_type, is_liquid: #is_liquid, + is_solid: #is_solid, } }); + tokens } } @@ -1001,7 +1019,9 @@ pub(crate) fn build() -> TokenStream { .iter() .map(|shape| shape.to_token_stream()); - let unique_states = unique_states.iter().map(|state| state.to_token_stream()); + let unique_states = unique_states + .iter() + .map(|state| state.to_tokens(&blocks_assets.shapes)); let block_props = block_properties.iter().map(|prop| prop.to_token_stream()); let properties = property_enums.values().map(|prop| prop.to_token_stream()); @@ -1065,6 +1085,7 @@ pub(crate) fn build() -> TokenStream { pub opacity: Option, pub block_entity_type: Option, pub is_liquid: bool, + pub is_solid: bool, } #[derive(Clone, Debug)] @@ -1081,6 +1102,7 @@ pub(crate) fn build() -> TokenStream { pub opacity: Option, pub block_entity_type: Option, pub is_liquid: bool, + pub is_solid: bool, } #[derive(Clone, Debug)] @@ -1124,22 +1146,6 @@ pub(crate) fn build() -> TokenStream { pub max: [f64; 3], } - #[derive(Clone, Copy, Debug)] - pub struct BlockStateData { - pub air: bool, - pub luminance: u8, - pub burnable: bool, - pub tool_required: bool, - pub hardness: f32, - pub sided_transparency: bool, - pub replaceable: bool, - pub collision_shapes: &'static [u16], - pub opacity: Option, - pub block_entity_type: Option, - pub is_liquid: bool, - } - - pub trait BlockProperties where Self: 'static { // Convert properties to an index (`0` to `N-1`). fn to_index(&self) -> u16; @@ -1257,6 +1263,7 @@ pub(crate) fn build() -> TokenStream { opacity: partial_state.opacity, block_entity_type: partial_state.block_entity_type, is_liquid: partial_state.is_liquid, + is_solid: partial_state.is_solid, } } } diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 7de276337..8cd4de547 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -34,6 +34,7 @@ mod initialize_world_border; mod keep_alive; mod level_event; mod login; +mod multi_block_update; mod open_screen; mod open_sign_editor; mod particle; @@ -116,6 +117,7 @@ pub use initialize_world_border::*; pub use keep_alive::*; pub use level_event::*; pub use login::*; +pub use multi_block_update::*; pub use open_screen::*; pub use open_sign_editor::*; pub use particle::*; diff --git a/pumpkin-protocol/src/client/play/multi_block_update.rs b/pumpkin-protocol/src/client/play/multi_block_update.rs new file mode 100644 index 000000000..c64e90a2d --- /dev/null +++ b/pumpkin-protocol/src/client/play/multi_block_update.rs @@ -0,0 +1,49 @@ +use pumpkin_data::packet::clientbound::PLAY_SECTION_BLOCKS_UPDATE; +use pumpkin_util::math::{ + position::{BlockPos, chunk_section_from_pos, pack_local_chunk_section}, + vector3::{self, Vector3}, +}; + +use pumpkin_macros::packet; +use serde::{Serialize, ser::SerializeTuple}; + +use crate::codec::{var_int::VarInt, var_long::VarLong}; + +#[packet(PLAY_SECTION_BLOCKS_UPDATE)] +pub struct CMultiBlockUpdate { + chunk_section: Vector3, + positions_to_state_ids: Vec<(i16, i32)>, +} + +impl CMultiBlockUpdate { + pub fn new(positions_to_state_ids: Vec<(BlockPos, u16)>) -> Self { + let chunk_section = chunk_section_from_pos(&positions_to_state_ids[0].0); + Self { + chunk_section, + positions_to_state_ids: positions_to_state_ids + .into_iter() + .map(|(position, state_id)| (pack_local_chunk_section(&position), state_id as i32)) + .collect(), + } + } +} + +impl Serialize for CMultiBlockUpdate { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut tuple = serializer.serialize_tuple(2 + self.positions_to_state_ids.len())?; + + tuple.serialize_element(&vector3::packed_chunk_pos(&self.chunk_section))?; + tuple.serialize_element(&VarInt::from(self.positions_to_state_ids.len() as i32))?; + + for (position, state_id) in &self.positions_to_state_ids { + let long = ((*state_id as u64) << 12) | (*position as u64); + let var_long = VarLong::from(long as i64); + tuple.serialize_element(&var_long)?; + } + + tuple.end() + } +} diff --git a/pumpkin-util/src/math/position.rs b/pumpkin-util/src/math/position.rs index 44525a78f..06860a0d9 100644 --- a/pumpkin-util/src/math/position.rs +++ b/pumpkin-util/src/math/position.rs @@ -1,4 +1,7 @@ -use super::vector3::Vector3; +use super::{ + get_section_cord, + vector3::{self, Vector3}, +}; use std::fmt; use std::hash::Hash; @@ -6,11 +9,15 @@ use crate::math::vector2::Vector2; use num_traits::Euclid; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] /// Aka Block Position pub struct BlockPos(pub Vector3); impl BlockPos { + pub fn new(x: i32, y: i32, z: i32) -> Self { + Self(Vector3::new(x, y, z)) + } + pub fn chunk_and_chunk_relative_position(&self) -> (Vector2, Vector3) { let (z_chunk, z_rem) = self.0.z.div_rem_euclid(&16); let (x_chunk, x_rem) = self.0.x.div_rem_euclid(&16); @@ -28,6 +35,18 @@ impl BlockPos { }; (chunk_coordinate, relative) } + pub fn section_relative_position(&self) -> Vector3 { + let (_z_chunk, z_rem) = self.0.z.div_rem_euclid(&16); + let (_x_chunk, x_rem) = self.0.x.div_rem_euclid(&16); + let (_y_chunk, y_rem) = self.0.y.div_rem_euclid(&16); + + // Since we divide by 16 remnant can never exceed u8 + Vector3 { + x: x_rem, + z: z_rem, + y: y_rem, + } + } pub fn from_i64(encoded_position: i64) -> Self { BlockPos(Vector3 { x: (encoded_position >> 38) as i32, @@ -55,6 +74,14 @@ impl BlockPos { pub fn offset(&self, offset: Vector3) -> Self { BlockPos(self.0 + offset) } + + pub fn up(&self) -> Self { + self.offset(Vector3::new(0, 1, 0)) + } + + pub fn down(&self) -> Self { + self.offset(Vector3::new(0, -1, 0)) + } } impl Serialize for BlockPos { fn serialize(&self, serializer: S) -> Result @@ -99,3 +126,25 @@ impl fmt::Display for BlockPos { write!(f, "{}, {}, {}", self.0.x, self.0.y, self.0.z) } } + +#[must_use] +pub const fn chunk_section_from_pos(block_pos: &BlockPos) -> Vector3 { + let block_pos = block_pos.0; + Vector3::new( + get_section_cord(block_pos.x), + get_section_cord(block_pos.y), + get_section_cord(block_pos.z), + ) +} + +pub const fn get_local_cord(cord: i32) -> i32 { + cord & 15 +} + +#[must_use] +pub fn pack_local_chunk_section(block_pos: &BlockPos) -> i16 { + let x = get_local_cord(block_pos.0.x); + let z = get_local_cord(block_pos.0.z); + let y = get_local_cord(block_pos.0.y); + vector3::packed_local(&Vector3::new(x, y, z)) +} diff --git a/pumpkin-util/src/math/vector2.rs b/pumpkin-util/src/math/vector2.rs index 62ab639fc..3fbd5225c 100644 --- a/pumpkin-util/src/math/vector2.rs +++ b/pumpkin-util/src/math/vector2.rs @@ -115,3 +115,7 @@ impl Math for f32 {} impl Math for i32 {} impl Math for i64 {} impl Math for i8 {} + +pub const fn to_chunk_pos(vec: &Vector2) -> Vector2 { + Vector2::new(vec.x >> 4, vec.z >> 4) +} diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index c76213ddb..54ba1e801 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -308,3 +308,21 @@ impl serde::Serialize for Vector3 { serializer.serialize_bytes(&buf) } } + +#[inline] +pub const fn packed_chunk_pos(vec: &Vector3) -> i64 { + let mut result = 0i64; + // Need to go to i64 first to conserve sign + result |= (vec.x as i64 & 0x3FFFFF) << 42; + result |= (vec.z as i64 & 0x3FFFFF) << 20; + result |= vec.y as i64 & 0xFFFFF; + result +} + +#[inline] +pub const fn packed_local(vec: &Vector3) -> i16 { + let x = vec.x as i16; + let y = vec.y as i16; + let z = vec.z as i16; + (x << 8) | (z << 4) | y +} diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index bdb011215..5dcb04bd7 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -3,13 +3,13 @@ pub mod registry; pub mod state; use num_derive::FromPrimitive; -use pumpkin_data::block::{Axis, HorizontalFacing}; +use pumpkin_data::block::{Axis, Facing, HorizontalFacing}; use pumpkin_util::math::vector3::Vector3; use serde::Deserialize; pub use state::ChunkBlockState; -#[derive(FromPrimitive, PartialEq, Clone, Copy)] +#[derive(FromPrimitive, PartialEq, Clone, Copy, Debug)] pub enum BlockDirection { Down = 0, Up, @@ -88,6 +88,17 @@ impl BlockDirection { ] } + pub fn abstract_block_update_order() -> [BlockDirection; 6] { + [ + BlockDirection::West, + BlockDirection::East, + BlockDirection::North, + BlockDirection::South, + BlockDirection::Down, + BlockDirection::Up, + ] + } + pub fn horizontal() -> [BlockDirection; 4] { [ BlockDirection::North, @@ -97,10 +108,29 @@ impl BlockDirection { ] } + pub fn is_horizontal(&self) -> bool { + matches!( + self, + BlockDirection::North + | BlockDirection::South + | BlockDirection::West + | BlockDirection::East + ) + } + pub fn vertical() -> [BlockDirection; 2] { [BlockDirection::Down, BlockDirection::Up] } + pub fn to_horizontal_facing(&self) -> Option { + match self { + BlockDirection::North => Some(HorizontalFacing::North), + BlockDirection::South => Some(HorizontalFacing::South), + BlockDirection::West => Some(HorizontalFacing::West), + BlockDirection::East => Some(HorizontalFacing::East), + _ => None, + } + } pub fn to_cardinal_direction(&self) -> HorizontalFacing { match self { BlockDirection::North => HorizontalFacing::North, @@ -127,6 +157,17 @@ impl BlockDirection { } } + pub fn to_facing(&self) -> Facing { + match self { + BlockDirection::North => Facing::North, + BlockDirection::South => Facing::South, + BlockDirection::West => Facing::West, + BlockDirection::East => Facing::East, + BlockDirection::Up => Facing::Up, + BlockDirection::Down => Facing::Down, + } + } + pub fn rotate_clockwise(&self) -> BlockDirection { match self { BlockDirection::North => BlockDirection::East, @@ -138,3 +179,53 @@ impl BlockDirection { } } } + +pub trait HorizontalFacingExt { + fn to_block_direction(&self) -> BlockDirection; + fn rotate(&self) -> HorizontalFacing; + fn rotate_ccw(&self) -> HorizontalFacing; +} + +impl HorizontalFacingExt for HorizontalFacing { + fn to_block_direction(&self) -> BlockDirection { + match self { + HorizontalFacing::North => BlockDirection::North, + HorizontalFacing::South => BlockDirection::South, + HorizontalFacing::West => BlockDirection::West, + HorizontalFacing::East => BlockDirection::East, + } + } + fn rotate(&self) -> HorizontalFacing { + match self { + HorizontalFacing::North => HorizontalFacing::East, + HorizontalFacing::South => HorizontalFacing::West, + HorizontalFacing::West => HorizontalFacing::North, + HorizontalFacing::East => HorizontalFacing::South, + } + } + fn rotate_ccw(&self) -> HorizontalFacing { + match self { + HorizontalFacing::North => HorizontalFacing::West, + HorizontalFacing::South => HorizontalFacing::East, + HorizontalFacing::West => HorizontalFacing::North, + HorizontalFacing::East => HorizontalFacing::South, + } + } +} + +pub trait FacingExt { + fn to_block_direction(&self) -> BlockDirection; +} + +impl FacingExt for Facing { + fn to_block_direction(&self) -> BlockDirection { + match self { + Facing::North => BlockDirection::North, + Facing::South => BlockDirection::South, + Facing::West => BlockDirection::West, + Facing::East => BlockDirection::East, + Facing::Up => BlockDirection::Up, + Facing::Down => BlockDirection::Down, + } + } +} diff --git a/pumpkin-world/src/chunk/format/anvil.rs b/pumpkin-world/src/chunk/format/anvil.rs index a7a2c160c..39472817a 100644 --- a/pumpkin-world/src/chunk/format/anvil.rs +++ b/pumpkin-world/src/chunk/format/anvil.rs @@ -24,7 +24,9 @@ use crate::chunk::{ io::{ChunkSerializer, LoadedData}, }; -use super::{ChunkNbt, ChunkSection, ChunkSectionBlockStates, PaletteEntry}; +use super::{ + ChunkNbt, ChunkSection, ChunkSectionBlockStates, PaletteEntry, SerializedScheduledTick, +}; /// The side size of a region in chunks (one region is 32x32 chunks) pub const REGION_SIZE: usize = 32; @@ -884,6 +886,40 @@ pub fn chunk_to_bytes(chunk_data: &ChunkData) -> Result, ChunkSerializin status: ChunkStatus::Full, heightmaps: chunk_data.heightmap.clone(), sections, + block_ticks: { + chunk_data + .block_ticks + .iter() + .map(|tick| SerializedScheduledTick { + x: tick.block_pos.0.x, + y: tick.block_pos.0.y, + z: tick.block_pos.0.z, + delay: tick.delay as i32, + priority: tick.priority as i32, + target_block: format!( + "minecraft:{}", + Block::from_id(tick.target_block_id).unwrap().name + ), + }) + .collect() + }, + fluid_ticks: { + chunk_data + .fluid_ticks + .iter() + .map(|tick| SerializedScheduledTick { + x: tick.block_pos.0.x, + y: tick.block_pos.0.y, + z: tick.block_pos.0.z, + delay: tick.delay as i32, + priority: tick.priority as i32, + target_block: format!( + "minecraft:{}", + Block::from_id(tick.target_block_id).unwrap().name + ), + }) + .collect() + }, }; let mut result = Vec::new(); diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs index 69b5832d0..a777aa5d0 100644 --- a/pumpkin-world/src/chunk/format/mod.rs +++ b/pumpkin-world/src/chunk/format/mod.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; -use pumpkin_data::chunk::ChunkStatus; +use pumpkin_data::{block::Block, chunk::ChunkStatus}; use pumpkin_nbt::{from_bytes, nbt_long_array}; -use pumpkin_util::math::{ceil_log2, vector2::Vector2}; +use pumpkin_util::math::{ceil_log2, position::BlockPos, vector2::Vector2}; use serde::{Deserialize, Serialize}; use crate::{ @@ -13,6 +13,7 @@ use crate::{ use super::{ CHUNK_AREA, ChunkBlocks, ChunkData, ChunkHeightmaps, ChunkParsingError, SUBCHUNK_VOLUME, + ScheduledTick, TickPriority, }; pub mod anvil; @@ -119,6 +120,34 @@ impl ChunkData { position, // This chunk is read from disk, so it has not been modified dirty: false, + block_ticks: chunk_data + .block_ticks + .iter() + .map(|tick| ScheduledTick { + block_pos: BlockPos::new(tick.x, tick.y, tick.z), + delay: tick.delay as u16, + priority: TickPriority::from(tick.priority), + target_block_id: Block::from_registry_key( + &tick.target_block.replace("minecraft:", ""), + ) + .unwrap_or(Block::AIR) + .id, + }) + .collect(), + fluid_ticks: chunk_data + .fluid_ticks + .iter() + .map(|tick| ScheduledTick { + block_pos: BlockPos::new(tick.x, tick.y, tick.z), + delay: tick.delay as u16, + priority: TickPriority::from(tick.priority), + target_block_id: Block::from_registry_key( + &tick.target_block.replace("minecraft:", ""), + ) + .unwrap_or(Block::AIR) + .id, + }) + .collect(), }) } } @@ -150,6 +179,22 @@ struct ChunkSectionBlockStates { palette: Vec, } +#[derive(Serialize, Deserialize, Debug)] +struct SerializedScheduledTick { + #[serde(rename = "x")] + x: i32, + #[serde(rename = "y")] + y: i32, + #[serde(rename = "z")] + z: i32, + #[serde(rename = "t")] + delay: i32, + #[serde(rename = "p")] + priority: i32, + #[serde(rename = "i")] + target_block: String, +} + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] struct ChunkNbt { @@ -164,4 +209,8 @@ struct ChunkNbt { #[serde(rename = "sections")] sections: Vec, heightmaps: ChunkHeightmaps, + #[serde(rename = "block_ticks")] + block_ticks: Vec, + #[serde(rename = "fluid_ticks")] + fluid_ticks: Vec, } diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index d3cfec41e..19020f82f 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -1,5 +1,5 @@ use pumpkin_nbt::nbt_long_array; -use pumpkin_util::math::vector2::Vector2; +use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use serde::{Deserialize, Serialize}; use std::iter::repeat_with; use thiserror::Error; @@ -56,7 +56,55 @@ pub enum CompressionError { ZstdError(std::io::Error), } -#[derive(Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] +#[repr(i32)] +pub enum TickPriority { + ExtremelyHigh = -3, + VeryHigh = -2, + High = -1, + Normal = 0, + Low = 1, + VeryLow = 2, + ExtremelyLow = 3, +} + +impl TickPriority { + pub fn values() -> [TickPriority; 7] { + [ + TickPriority::ExtremelyHigh, + TickPriority::VeryHigh, + TickPriority::High, + TickPriority::Normal, + TickPriority::Low, + TickPriority::VeryLow, + TickPriority::ExtremelyLow, + ] + } +} + +impl From for TickPriority { + fn from(value: i32) -> Self { + match value { + -3 => TickPriority::ExtremelyHigh, + -2 => TickPriority::VeryHigh, + -1 => TickPriority::High, + 0 => TickPriority::Normal, + 1 => TickPriority::Low, + 2 => TickPriority::VeryLow, + 3 => TickPriority::ExtremelyLow, + _ => panic!("Invalid tick priority: {}", value), + } + } +} + +#[derive(Debug, Clone)] +pub struct ScheduledTick { + pub block_pos: BlockPos, + pub delay: u16, + pub priority: TickPriority, + pub target_block_id: u16, +} + pub struct ChunkData { /// See description in [`ChunkBlocks`] pub blocks: ChunkBlocks, @@ -64,6 +112,8 @@ pub struct ChunkData { pub heightmap: ChunkHeightmaps, pub position: Vector2, pub dirty: bool, + pub block_ticks: Vec, + pub fluid_ticks: Vec, } /// Represents pure block data for a chunk. @@ -262,7 +312,6 @@ impl ChunkData { todo!() } } - #[derive(Error, Debug)] pub enum ChunkParsingError { #[error("Failed reading chunk status {0}")] diff --git a/pumpkin-world/src/generation/generic_generator.rs b/pumpkin-world/src/generation/generic_generator.rs index a233fa17c..7a3ed6c32 100644 --- a/pumpkin-world/src/generation/generic_generator.rs +++ b/pumpkin-world/src/generation/generic_generator.rs @@ -78,6 +78,8 @@ impl WorldGenerator for GenericGen position: at, // This chunk was just created! We want to say its been changed dirty: true, + block_ticks: vec![], + fluid_ticks: vec![], } } } diff --git a/pumpkin-world/src/generation/implementation/mod.rs b/pumpkin-world/src/generation/implementation/mod.rs index 2b2ff7762..92603cc73 100644 --- a/pumpkin-world/src/generation/implementation/mod.rs +++ b/pumpkin-world/src/generation/implementation/mod.rs @@ -77,6 +77,8 @@ impl WorldGenerator for VanillaGenerator { heightmap: Default::default(), position: at, dirty: true, + block_ticks: Default::default(), + fluid_ticks: Default::default(), } } } diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index a0a363898..28916f1b8 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -4,16 +4,16 @@ use dashmap::{DashMap, Entry}; use log::trace; use num_traits::Zero; use pumpkin_config::{advanced_config, chunk::ChunkFormat}; -use pumpkin_util::math::vector2::Vector2; +use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use tokio::{ - sync::{Notify, RwLock, mpsc}, + sync::{Mutex, Notify, RwLock, mpsc}, task::{JoinHandle, JoinSet}, }; use tokio_util::task::TaskTracker; use crate::{ chunk::{ - ChunkData, ChunkParsingError, ChunkReadingError, + ChunkData, ChunkParsingError, ChunkReadingError, ScheduledTick, TickPriority, format::{anvil::AnvilChunkFile, linear::LinearFile}, io::{ChunkIO, LoadedData, chunk_file_manager::ChunkFileManager}, }, @@ -55,6 +55,7 @@ pub struct Level { // Gets unlocked when dropped // TODO: Make this a trait _locker: Arc, + block_ticks: Arc>>, /// Tracks tasks associated with this world instance tasks: TaskTracker, /// Notification that interrupts tasks for shutdown @@ -134,6 +135,7 @@ impl Level { _locker: Arc::new(locker), tasks: TaskTracker::new(), shutdown_notifier: Notify::new(), + block_ticks: Arc::new(Mutex::new(Vec::new())), } } @@ -340,6 +342,24 @@ impl Level { if chunks_to_write.is_empty() { return; } + let mut block_ticks = self.block_ticks.lock().await; + + for (coord, chunk) in &chunks_to_write { + let mut chunk_data = chunk.write().await; + chunk_data.block_ticks.clear(); + // Only keep ticks that are not saved in the chunk + block_ticks.retain(|tick| { + let (chunk_coord, _relative_coord) = + tick.block_pos.chunk_and_chunk_relative_position(); + if chunk_coord == *coord { + chunk_data.block_ticks.push(tick.clone()); + false + } else { + true + } + }); + } + drop(block_ticks); let chunk_saver = self.chunk_saver.clone(); let level_folder = self.level_folder.clone(); @@ -417,11 +437,19 @@ impl Level { let load_channel = channel.clone(); let loaded_chunks = self.loaded_chunks.clone(); + let level_block_ticks = self.block_ticks.clone(); let handle_load = async move { while let Some(data) = load_bridge_recv.recv().await { match data { LoadedData::Loaded(chunk) => { let position = chunk.read().await.position; + + // Load the block ticks from the chunk + let block_ticks = chunk.read().await.block_ticks.clone(); + let mut level_block_ticks = level_block_ticks.lock().await; + level_block_ticks.extend(block_ticks); + drop(level_block_ticks); + let value = loaded_chunks .entry(position) .or_insert(chunk) @@ -491,4 +519,50 @@ impl Level { .await; let _ = set.join_all().await; } + + pub fn try_get_chunk( + &self, + coordinates: Vector2, + ) -> Option, Arc>>> { + self.loaded_chunks.try_get(&coordinates).try_unwrap() + } + + pub async fn get_and_tick_block_ticks(&self) -> Vec { + let mut block_ticks = self.block_ticks.lock().await; + let mut ticks = Vec::new(); + block_ticks.retain_mut(|tick| { + tick.delay = tick.delay.saturating_sub(1); + if tick.delay == 0 { + ticks.push(tick.clone()); + false + } else { + true + } + }); + ticks.sort_by_key(|tick| tick.priority); + ticks + } + + pub async fn is_block_tick_scheduled(&self, block_pos: &BlockPos, block_id: u16) -> bool { + let block_ticks = self.block_ticks.lock().await; + block_ticks + .iter() + .any(|tick| tick.block_pos == *block_pos && tick.target_block_id == block_id) + } + + pub async fn schedule_block_tick( + &self, + block_id: u16, + block_pos: &BlockPos, + delay: u16, + priority: TickPriority, + ) { + let mut block_ticks = self.block_ticks.lock().await; + block_ticks.push(ScheduledTick { + block_pos: *block_pos, + delay, + priority, + target_block_id: block_id, + }); + } } diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 29640dd12..ee66e7115 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -81,6 +81,8 @@ chrono = { version = "0.4", features = ["serde"] } # plugins libloading = "0.8" +bitflags = "2.9.0" +rustc-hash = "2.1.1" # Task handling tokio-util = { version = "0.7.14", features = ["rt"] } diff --git a/pumpkin/src/block/blocks/doors.rs b/pumpkin/src/block/blocks/doors.rs index ffa0f1759..f60c062a4 100644 --- a/pumpkin/src/block/blocks/doors.rs +++ b/pumpkin/src/block/blocks/doors.rs @@ -16,6 +16,7 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::block::registry::BlockActionResult; use crate::block::registry::BlockRegistry; use crate::entity::player::Player; +use crate::world::BlockFlags; use pumpkin_data::item::Item; use pumpkin_protocol::server::play::SUseItemOn; @@ -40,10 +41,18 @@ async fn toggle_door(world: &World, block_pos: &BlockPos) { other_door_props.open = door_props.open; world - .set_block_state(block_pos, door_props.to_state_id(&block)) + .set_block_state( + block_pos, + door_props.to_state_id(&block), + BlockFlags::NOTIFY_LISTENERS, + ) .await; world - .set_block_state(&other_pos, other_door_props.to_state_id(&other_block)) + .set_block_state( + &other_pos, + other_door_props.to_state_id(&other_block), + BlockFlags::NOTIFY_LISTENERS, + ) .await; } @@ -94,15 +103,7 @@ pub fn register_door_blocks(manager: &mut BlockRegistry) { door_props.to_state_id(block) } - async fn can_place( - &self, - _server: &Server, - world: &World, - _block: &Block, - _face: &BlockDirection, - block_pos: &BlockPos, - _player_direction: &HorizontalFacing, - ) -> bool { + async fn can_place_at(&self, world: &World, block_pos: &BlockPos) -> bool { if world .get_block_state(&block_pos.offset(BlockDirection::Up.to_offset())) .await @@ -115,20 +116,21 @@ pub fn register_door_blocks(manager: &mut BlockRegistry) { async fn placed( &self, - block: &Block, - _player: &Player, - location: BlockPos, - _server: &Server, world: &World, + block: &Block, + state_id: u16, + block_pos: &BlockPos, + _old_state_id: u16, + _notify: bool, ) { - let state_id = world.get_block_state_id(&location).await.unwrap(); let mut door_props = DoorProperties::from_state_id(state_id, block); door_props.half = DoubleBlockHalf::Upper; world .set_block_state( - &location.offset(BlockDirection::Up.to_offset()), + &block_pos.offset(BlockDirection::Up.to_offset()), door_props.to_state_id(block), + BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK, ) .await; } @@ -138,7 +140,7 @@ pub fn register_door_blocks(manager: &mut BlockRegistry) { block: &Block, _player: &Player, location: BlockPos, - server: &Server, + _server: &Server, world: Arc, state: BlockState, ) { @@ -154,7 +156,7 @@ pub fn register_door_blocks(manager: &mut BlockRegistry) { if let Ok(other_block) = world.get_block(&other_pos).await { if other_block.id == block.id { world - .break_block(&other_pos, None, true, Some(server)) + .break_block(&other_pos, None, BlockFlags::NOTIFY_NEIGHBORS) .await; } } diff --git a/pumpkin/src/block/blocks/fence_gates.rs b/pumpkin/src/block/blocks/fence_gates.rs index bb86f69dc..f1babf523 100644 --- a/pumpkin/src/block/blocks/fence_gates.rs +++ b/pumpkin/src/block/blocks/fence_gates.rs @@ -13,6 +13,7 @@ use crate::block::registry::BlockActionResult; use crate::block::registry::BlockRegistry; use crate::entity::player::Player; use crate::server::Server; +use crate::world::BlockFlags; use crate::world::World; use pumpkin_data::item::Item; @@ -24,7 +25,11 @@ pub async fn toggle_fence_gate(world: &World, block_pos: &BlockPos) -> u16 { let mut fence_gate_props = FenceGateProperties::from_state_id(state.id, &block); fence_gate_props.open = fence_gate_props.open.flip(); world - .set_block_state(block_pos, fence_gate_props.to_state_id(&block)) + .set_block_state( + block_pos, + fence_gate_props.to_state_id(&block), + BlockFlags::NOTIFY_LISTENERS, + ) .await; fence_gate_props.to_state_id(&block) diff --git a/pumpkin/src/block/blocks/fences.rs b/pumpkin/src/block/blocks/fences.rs index 25dad5249..194b97874 100644 --- a/pumpkin/src/block/blocks/fences.rs +++ b/pumpkin/src/block/blocks/fences.rs @@ -96,18 +96,17 @@ pub fn register_fence_blocks(manager: &mut BlockRegistry) { fence_state(world, block, block_pos).await } - async fn on_neighbor_update( + async fn get_state_for_neighbor_update( &self, - _server: &Server, world: &World, block: &Block, + _state: u16, block_pos: &BlockPos, - _source_face: &BlockDirection, - _source_block_pos: &BlockPos, - ) { - world - .set_block_state(block_pos, fence_state(world, block, block_pos).await) - .await; + _direction: &BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: u16, + ) -> u16 { + fence_state(world, block, block_pos).await } } diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index 864668583..44f8babad 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -4,7 +4,7 @@ use crate::block::pumpkin_block::PumpkinBlock; use crate::block::registry::BlockActionResult; use crate::entity::player::Player; use crate::server::Server; -use crate::world::World; +use crate::world::{BlockFlags, World}; use async_trait::async_trait; use pumpkin_data::block::{Block, BlockProperties, BlockState, Boolean, JukeboxLikeProperties}; use pumpkin_data::item::Item; @@ -34,7 +34,7 @@ impl JukeboxBlock { }, }; world - .set_block_state(&location, new_state.to_state_id(block)) + .set_block_state(&location, new_state.to_state_id(block), BlockFlags::empty()) .await; } diff --git a/pumpkin/src/block/blocks/lever.rs b/pumpkin/src/block/blocks/lever.rs deleted file mode 100644 index 842a0fe4f..000000000 --- a/pumpkin/src/block/blocks/lever.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::entity::player::Player; -use async_trait::async_trait; -use pumpkin_data::block::{Block, BlockFace, LeverLikeProperties}; -use pumpkin_data::{ - block::{BlockProperties, HorizontalFacing}, - item::Item, -}; -use pumpkin_macros::pumpkin_block; -use pumpkin_protocol::server::play::SUseItemOn; -use pumpkin_util::math::position::BlockPos; -use pumpkin_world::block::BlockDirection; - -use crate::{ - block::{pumpkin_block::PumpkinBlock, registry::BlockActionResult}, - server::Server, - world::World, -}; - -async fn toggle_lever(world: &World, block_pos: &BlockPos) { - let (block, state) = world.get_block_and_block_state(block_pos).await.unwrap(); - - let mut lever_props = LeverLikeProperties::from_state_id(state.id, &block); - lever_props.powered = lever_props.powered.flip(); - world - .set_block_state(block_pos, lever_props.to_state_id(&block)) - .await; -} - -#[pumpkin_block("minecraft:lever")] -pub struct LeverBlock; - -#[async_trait] -impl PumpkinBlock for LeverBlock { - 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 mut lever_props = LeverLikeProperties::from_state_id(block.default_state_id, block); - - match face { - BlockDirection::Up => lever_props.face = BlockFace::Ceiling, - BlockDirection::Down => lever_props.face = BlockFace::Floor, - _ => lever_props.face = BlockFace::Wall, - } - - if face == &BlockDirection::Up || face == &BlockDirection::Down { - lever_props.facing = *player_direction; - } else { - lever_props.facing = face.opposite().to_cardinal_direction(); - }; - - lever_props.to_state_id(block) - } - - async fn use_with_item( - &self, - _block: &Block, - _player: &Player, - location: BlockPos, - _item: &Item, - _server: &Server, - world: &World, - ) -> BlockActionResult { - toggle_lever(world, &location).await; - BlockActionResult::Consume - } - - async fn normal_use( - &self, - _block: &Block, - _player: &Player, - location: BlockPos, - _server: &Server, - world: &World, - ) { - toggle_lever(world, &location).await; - } -} diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 86d59b010..3a9a119b2 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -12,10 +12,10 @@ pub(crate) mod fence_gates; pub(crate) mod fences; pub(crate) mod furnace; pub(crate) mod jukebox; -pub(crate) mod lever; pub(crate) mod logs; +pub(crate) mod redstone; pub(crate) mod tnt; - +pub(crate) mod torches; /// The standard destroy with container removes the player forcibly from the container, /// drops items to the floor, and back to the player's inventory if the item stack is in movement. pub async fn standard_on_broken_with_container( diff --git a/pumpkin/src/block/blocks/redstone/buttons.rs b/pumpkin/src/block/blocks/redstone/buttons.rs new file mode 100644 index 000000000..93324d4bf --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/buttons.rs @@ -0,0 +1,189 @@ +use async_trait::async_trait; +use pumpkin_data::block::Block; +use pumpkin_data::block::BlockFace; +use pumpkin_data::block::BlockState; +use pumpkin_data::block::HorizontalFacing; +use pumpkin_data::block::{BlockProperties, Boolean}; +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::chunk::TickPriority; + +type ButtonLikeProperties = pumpkin_data::block::LeverLikeProperties; + +use crate::block::blocks::redstone::lever::LeverLikePropertiesExt; +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::block::registry::BlockRegistry; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::BlockFlags; +use crate::world::World; + +#[allow(clippy::too_many_lines)] +pub fn register_button_blocks(manager: &mut BlockRegistry) { + let tag_values: &'static [&'static str] = + get_tag_values(RegistryKey::Block, "minecraft:buttons").unwrap(); + + for block in tag_values { + async fn click_button(world: &World, block_pos: &BlockPos) { + let (block, state) = world.get_block_and_block_state(block_pos).await.unwrap(); + + let mut button_props = ButtonLikeProperties::from_state_id(state.id, &block); + if !button_props.powered.to_bool() { + button_props.powered = Boolean::True; + world + .set_block_state( + block_pos, + button_props.to_state_id(&block), + BlockFlags::NOTIFY_ALL, + ) + .await; + let delay = if block == Block::STONE_BUTTON { 20 } else { 30 }; + world + .schedule_block_tick(&block, *block_pos, delay, TickPriority::Normal) + .await; + ButtonBlock::update_neighbors(world, block_pos, &button_props).await; + } + } + + pub struct ButtonBlock { + id: &'static str, + } + impl BlockMetadata for ButtonBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn id(&self) -> &'static str { + self.id + } + } + + #[async_trait] + impl PumpkinBlock for ButtonBlock { + 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 mut props = ButtonLikeProperties::default(block); + + match face { + BlockDirection::Up => props.face = BlockFace::Ceiling, + BlockDirection::Down => props.face = BlockFace::Floor, + _ => props.face = BlockFace::Wall, + } + + if face == &BlockDirection::Up || face == &BlockDirection::Down { + props.facing = *player_direction; + } else { + props.facing = face.opposite().to_cardinal_direction(); + }; + + props.to_state_id(block) + } + + async fn normal_use( + &self, + _block: &Block, + _player: &Player, + location: BlockPos, + _server: &Server, + world: &World, + ) { + click_button(world, &location).await; + } + + async fn on_scheduled_tick(&self, world: &World, block: &Block, block_pos: &BlockPos) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut props = ButtonLikeProperties::from_state_id(state.id, block); + props.powered = Boolean::False; + world + .set_block_state(block_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL) + .await; + Self::update_neighbors(world, block_pos, &props).await; + } + + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: &BlockDirection, + ) -> bool { + true + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + _direction: &BlockDirection, + ) -> u8 { + let button_props = ButtonLikeProperties::from_state_id(state.id, block); + if button_props.powered.to_bool() { + 15 + } else { + 0 + } + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let button_props = ButtonLikeProperties::from_state_id(state.id, block); + if button_props.powered.to_bool() && button_props.get_direction() == *direction { + 15 + } else { + 0 + } + } + + async fn on_state_replaced( + &self, + world: &World, + block: &Block, + location: BlockPos, + old_state_id: u16, + moved: bool, + ) { + if !moved { + let button_props = ButtonLikeProperties::from_state_id(old_state_id, block); + if button_props.powered.to_bool() { + Self::update_neighbors(world, &location, &button_props).await; + } + } + } + } + + impl ButtonBlock { + async fn update_neighbors( + world: &World, + block_pos: &BlockPos, + props: &ButtonLikeProperties, + ) { + let direction = props.get_direction().opposite(); + world.update_neighbors(block_pos, None).await; + world + .update_neighbors(&block_pos.offset(direction.to_offset()), None) + .await; + } + } + + manager.register(ButtonBlock { id: block }); + } +} diff --git a/pumpkin/src/block/blocks/redstone/lever.rs b/pumpkin/src/block/blocks/redstone/lever.rs new file mode 100644 index 000000000..f764d50c4 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/lever.rs @@ -0,0 +1,173 @@ +use crate::entity::player::Player; +use crate::world::BlockFlags; +use async_trait::async_trait; +use pumpkin_data::block::{Block, BlockFace, BlockState, LeverLikeProperties}; +use pumpkin_data::{ + block::{BlockProperties, HorizontalFacing}, + item::Item, +}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::{BlockDirection, HorizontalFacingExt}; + +use crate::{ + block::{pumpkin_block::PumpkinBlock, registry::BlockActionResult}, + server::Server, + world::World, +}; + +async fn toggle_lever(world: &World, block_pos: &BlockPos) { + let (block, state) = world.get_block_and_block_state(block_pos).await.unwrap(); + + let mut lever_props = LeverLikeProperties::from_state_id(state.id, &block); + lever_props.powered = lever_props.powered.flip(); + world + .set_block_state( + block_pos, + lever_props.to_state_id(&block), + BlockFlags::NOTIFY_ALL, + ) + .await; + + LeverBlock::update_neighbors(world, block_pos, &lever_props).await; +} + +#[pumpkin_block("minecraft:lever")] +pub struct LeverBlock; + +#[async_trait] +impl PumpkinBlock for LeverBlock { + 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 mut lever_props = LeverLikeProperties::from_state_id(block.default_state_id, block); + + match face { + BlockDirection::Up => lever_props.face = BlockFace::Ceiling, + BlockDirection::Down => lever_props.face = BlockFace::Floor, + _ => lever_props.face = BlockFace::Wall, + } + + if face == &BlockDirection::Up || face == &BlockDirection::Down { + lever_props.facing = *player_direction; + } else { + lever_props.facing = face.opposite().to_cardinal_direction(); + }; + + lever_props.to_state_id(block) + } + + async fn use_with_item( + &self, + _block: &Block, + _player: &Player, + location: BlockPos, + _item: &Item, + _server: &Server, + world: &World, + ) -> BlockActionResult { + toggle_lever(world, &location).await; + BlockActionResult::Consume + } + + async fn normal_use( + &self, + _block: &Block, + _player: &Player, + location: BlockPos, + _server: &Server, + world: &World, + ) { + toggle_lever(world, &location).await; + } + + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: &BlockDirection, + ) -> bool { + true + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + _direction: &BlockDirection, + ) -> u8 { + let lever_props = LeverLikeProperties::from_state_id(state.id, block); + if lever_props.powered.to_bool() { 15 } else { 0 } + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let lever_props = LeverLikeProperties::from_state_id(state.id, block); + if lever_props.powered.to_bool() && lever_props.get_direction() == *direction { + 15 + } else { + 0 + } + } + + async fn on_state_replaced( + &self, + world: &World, + block: &Block, + location: BlockPos, + old_state_id: u16, + moved: bool, + ) { + if !moved { + let lever_props = LeverLikeProperties::from_state_id(old_state_id, block); + if lever_props.powered.to_bool() { + Self::update_neighbors(world, &location, &lever_props).await; + } + } + } +} + +impl LeverBlock { + async fn update_neighbors( + world: &World, + block_pos: &BlockPos, + lever_props: &LeverLikeProperties, + ) { + let direction = lever_props.get_direction().opposite(); + world.update_neighbors(block_pos, None).await; + world + .update_neighbors(&block_pos.offset(direction.to_offset()), None) + .await; + } +} + +pub trait LeverLikePropertiesExt { + fn get_direction(&self) -> BlockDirection; +} + +impl LeverLikePropertiesExt for LeverLikeProperties { + fn get_direction(&self) -> BlockDirection { + match self.face { + BlockFace::Ceiling => BlockDirection::Down, + BlockFace::Floor => BlockDirection::Up, + BlockFace::Wall => self.facing.to_block_direction(), + } + } +} diff --git a/pumpkin/src/block/blocks/redstone/mod.rs b/pumpkin/src/block/blocks/redstone/mod.rs new file mode 100644 index 000000000..f27fe6ab1 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/mod.rs @@ -0,0 +1,178 @@ +/** + * This implementation is heavily based on + * Updated to fit pumpkin by 4lve + */ +use pumpkin_data::block::{Block, BlockState}; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::BlockDirection; + +use crate::world::World; + +pub(crate) mod buttons; +pub(crate) mod lever; +pub(crate) mod observer; +pub(crate) mod piston; +pub(crate) mod redstone_block; +pub(crate) mod redstone_lamp; +pub(crate) mod redstone_torch; +pub(crate) mod redstone_wire; +pub(crate) mod repeater; +pub(crate) mod target_block; +pub(crate) mod turbo; + +pub async fn update_wire_neighbors(world: &World, pos: BlockPos) { + for direction in &BlockDirection::all() { + let neighbor_pos = pos.offset(direction.to_offset()); + let block = world.get_block(&neighbor_pos).await.unwrap(); + world + .block_registry + .on_neighbor_update(world, &block, &neighbor_pos, &block, true) + .await; + for n_direction in &BlockDirection::all() { + let n_neighbor_pos = neighbor_pos.offset(n_direction.to_offset()); + let block = world.get_block(&n_neighbor_pos).await.unwrap(); + world + .block_registry + .on_neighbor_update(world, &block, &n_neighbor_pos, &block, true) + .await; + } + } +} + +pub async fn get_redstone_power( + block: &Block, + state: &BlockState, + world: &World, + pos: BlockPos, + facing: BlockDirection, +) -> u8 { + if state.is_solid { + return std::cmp::max( + get_max_strong_power(world, pos, true).await, + get_weak_power(block, state, world, pos, facing, true).await, + ); + } + get_weak_power(block, state, world, pos, facing, true).await +} + +async fn get_redstone_power_no_dust( + block: &Block, + state: &BlockState, + world: &World, + pos: BlockPos, + facing: BlockDirection, +) -> u8 { + if state.is_solid { + return std::cmp::max( + get_max_strong_power(world, pos, false).await, + get_weak_power(block, state, world, pos, facing, false).await, + ); + } + get_weak_power(block, state, world, pos, facing, false).await +} + +async fn get_max_strong_power(world: &World, pos: BlockPos, dust_power: bool) -> u8 { + let mut max_power = 0; + for side in &BlockDirection::all() { + let (block, state) = world + .get_block_and_block_state(&pos.offset(side.to_offset())) + .await + .unwrap(); + max_power = max_power.max( + get_strong_power( + &block, + &state, + world, + pos.offset(side.to_offset()), + *side, + dust_power, + ) + .await, + ); + } + max_power +} + +async fn get_max_weak_power(world: &World, pos: BlockPos, dust_power: bool) -> u8 { + let mut max_power = 0; + for side in &BlockDirection::all() { + let (block, state) = world + .get_block_and_block_state(&pos.offset(side.to_offset())) + .await + .unwrap(); + max_power = max_power.max( + get_weak_power( + &block, + &state, + world, + pos.offset(side.to_offset()), + *side, + dust_power, + ) + .await, + ); + } + max_power +} + +async fn get_weak_power( + block: &Block, + state: &BlockState, + world: &World, + pos: BlockPos, + side: BlockDirection, + dust_power: bool, +) -> u8 { + if !dust_power && *block == Block::REDSTONE_WIRE { + return 0; + } + world + .block_registry + .get_weak_redstone_power(block, world, &pos, state, &side) + .await +} + +async fn get_strong_power( + block: &Block, + state: &BlockState, + world: &World, + pos: BlockPos, + side: BlockDirection, + dust_power: bool, +) -> u8 { + if !dust_power && *block == Block::REDSTONE_WIRE { + return 0; + } + world + .block_registry + .get_strong_redstone_power(block, world, &pos, state, &side) + .await +} + +pub async fn block_receives_redstone_power(world: &World, pos: BlockPos) -> bool { + for face in &BlockDirection::all() { + let neighbor_pos = pos.offset(face.to_offset()); + let (block, state) = world + .get_block_and_block_state(&neighbor_pos) + .await + .unwrap(); + if get_redstone_power(&block, &state, world, neighbor_pos, *face).await > 0 { + return true; + } + } + false +} + +pub fn is_diode(block: &Block) -> bool { + *block == Block::REPEATER || *block == Block::COMPARATOR +} + +pub async fn diode_get_input_strength(world: &World, pos: BlockPos, facing: BlockDirection) -> u8 { + let input_pos = pos.offset(facing.to_offset()); + let (input_block, input_state) = world.get_block_and_block_state(&input_pos).await.unwrap(); + let power: u8 = get_redstone_power(&input_block, &input_state, world, input_pos, facing).await; + if power == 0 && input_state.is_solid { + return get_max_weak_power(world, input_pos, true).await; + } + power +} diff --git a/pumpkin/src/block/blocks/redstone/observer.rs b/pumpkin/src/block/blocks/redstone/observer.rs new file mode 100644 index 000000000..b24955f37 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/observer.rs @@ -0,0 +1,185 @@ +use async_trait::async_trait; +use pumpkin_data::block::{ + Block, BlockProperties, BlockState, Boolean, HorizontalFacing, ObserverLikeProperties, +}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::{ + block::{BlockDirection, FacingExt, HorizontalFacingExt}, + chunk::TickPriority, +}; + +use crate::{ + block::pumpkin_block::PumpkinBlock, + server::Server, + world::{BlockFlags, World}, +}; + +#[pumpkin_block("minecraft:observer")] +pub struct ObserverBlock; + +#[async_trait] +impl PumpkinBlock for ObserverBlock { + 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 mut props = ObserverLikeProperties::default(block); + props.facing = player_direction.to_block_direction().to_facing(); + props.to_state_id(block) + } + + async fn on_neighbor_update( + &self, + _world: &World, + _block: &Block, + _block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, + ) { + } + + async fn on_scheduled_tick(&self, world: &World, block: &Block, block_pos: &BlockPos) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut props = ObserverLikeProperties::from_state_id(state.id, block); + + if props.powered.to_bool() { + props.powered = Boolean::False; + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_LISTENERS, + ) + .await; + } else { + props.powered = Boolean::True; + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_LISTENERS, + ) + .await; + world + .schedule_block_tick(block, *block_pos, 2, TickPriority::Normal) + .await; + } + + Self::update_neighbors(world, block, block_pos, &props).await; + } + + async fn get_state_for_neighbor_update( + &self, + world: &World, + block: &Block, + state: u16, + block_pos: &BlockPos, + direction: &BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: u16, + ) -> u16 { + let props = ObserverLikeProperties::from_state_id(state, block); + + if props.facing.to_block_direction() == *direction && !props.powered.to_bool() { + Self::schedule_tick(world, block_pos).await; + } + + state + } + + async fn emits_redstone_power( + &self, + block: &Block, + state: &BlockState, + direction: &BlockDirection, + ) -> bool { + let props = ObserverLikeProperties::from_state_id(state.id, block); + props.facing.to_block_direction() == *direction + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let props = ObserverLikeProperties::from_state_id(state.id, block); + if props.facing.to_block_direction() == *direction && props.powered.to_bool() { + 15 + } else { + 0 + } + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + world: &World, + block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + self.get_weak_redstone_power(block, world, block_pos, state, direction) + .await + } + + async fn on_state_replaced( + &self, + world: &World, + block: &Block, + location: BlockPos, + old_state_id: u16, + moved: bool, + ) { + if !moved { + let props = ObserverLikeProperties::from_state_id(old_state_id, block); + if props.powered.to_bool() + && world + .is_block_tick_scheduled(&location, &Block::OBSERVER) + .await + { + Self::update_neighbors(world, block, &location, &props).await; + } + } + } +} + +impl ObserverBlock { + async fn update_neighbors( + world: &World, + block: &Block, + block_pos: &BlockPos, + props: &ObserverLikeProperties, + ) { + let facing = props.facing; + let opposite_facing_pos = + block_pos.offset(facing.to_block_direction().opposite().to_offset()); + world.update_neighbor(&opposite_facing_pos, block).await; + world + .update_neighbors(&opposite_facing_pos, Some(&facing.to_block_direction())) + .await; + } + + async fn schedule_tick(world: &World, block_pos: &BlockPos) { + if world + .is_block_tick_scheduled(block_pos, &Block::OBSERVER) + .await + { + return; + } + world + .schedule_block_tick(&Block::OBSERVER, *block_pos, 2, TickPriority::Normal) + .await; + } +} diff --git a/pumpkin/src/block/blocks/redstone/piston.rs b/pumpkin/src/block/blocks/redstone/piston.rs new file mode 100644 index 000000000..1a7b442b6 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/piston.rs @@ -0,0 +1,63 @@ +use async_trait::async_trait; +use pumpkin_data::block::{Block, BlockProperties, Boolean, HorizontalFacing}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::{BlockDirection, HorizontalFacingExt}; + +use crate::{ + block::pumpkin_block::PumpkinBlock, + server::Server, + world::{BlockFlags, World}, +}; + +use super::block_receives_redstone_power; + +type PistonProps = pumpkin_data::block::StickyPistonLikeProperties; + +#[pumpkin_block("minecraft:piston")] +pub struct PistonBlock; + +#[async_trait] +impl PumpkinBlock for PistonBlock { + 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 mut props = PistonProps::default(block); + props.extended = Boolean::from_bool(block_receives_redstone_power(world, *block_pos).await); + props.facing = player_direction.to_block_direction().to_facing(); + props.to_state_id(block) + } + + async fn on_neighbor_update( + &self, + world: &World, + block: &Block, + block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, + ) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut props = PistonProps::from_state_id(state.id, block); + let is_receiving_power = block_receives_redstone_power(world, *block_pos).await; + + if is_receiving_power { + props.extended = props.extended.flip(); + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_LISTENERS, + ) + .await; + } + } +} diff --git a/pumpkin/src/block/blocks/redstone/redstone_block.rs b/pumpkin/src/block/blocks/redstone/redstone_block.rs new file mode 100644 index 000000000..2f39f71e4 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/redstone_block.rs @@ -0,0 +1,33 @@ +use async_trait::async_trait; +use pumpkin_data::block::{Block, BlockState}; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::BlockDirection; + +use crate::{block::pumpkin_block::PumpkinBlock, world::World}; + +#[pumpkin_block("minecraft:redstone_block")] +pub struct RedstoneBlock; + +#[async_trait] +impl PumpkinBlock for RedstoneBlock { + async fn get_weak_redstone_power( + &self, + _block: &Block, + _world: &World, + _block_pos: &BlockPos, + _state: &BlockState, + _direction: &BlockDirection, + ) -> u8 { + 15 + } + + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: &BlockDirection, + ) -> bool { + true + } +} diff --git a/pumpkin/src/block/blocks/redstone/redstone_lamp.rs b/pumpkin/src/block/blocks/redstone/redstone_lamp.rs new file mode 100644 index 000000000..f142c8727 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/redstone_lamp.rs @@ -0,0 +1,87 @@ +use async_trait::async_trait; +use pumpkin_data::block::{Block, BlockProperties, Boolean, HorizontalFacing}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::{block::BlockDirection, chunk::TickPriority}; + +use crate::{ + block::pumpkin_block::PumpkinBlock, + server::Server, + world::{BlockFlags, World}, +}; + +use super::block_receives_redstone_power; + +type RedstoneLampProperties = pumpkin_data::block::RedstoneOreLikeProperties; + +#[pumpkin_block("minecraft:redstone_lamp")] +pub struct RedstoneLamp; + +#[async_trait] +impl PumpkinBlock for RedstoneLamp { + 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 mut props = RedstoneLampProperties::default(block); + props.lit = Boolean::from_bool(block_receives_redstone_power(world, *block_pos).await); + props.to_state_id(block) + } + + async fn on_neighbor_update( + &self, + world: &World, + block: &Block, + block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, + ) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut props = RedstoneLampProperties::from_state_id(state.id, block); + let is_lit = props.lit.to_bool(); + let is_receiving_power = block_receives_redstone_power(world, *block_pos).await; + + if is_lit != is_receiving_power { + if is_lit { + world + .schedule_block_tick(block, *block_pos, 4, TickPriority::Normal) + .await; + } else { + props.lit = props.lit.flip(); + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_LISTENERS, + ) + .await; + } + } + } + + async fn on_scheduled_tick(&self, world: &World, block: &Block, block_pos: &BlockPos) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut props = RedstoneLampProperties::from_state_id(state.id, block); + let is_lit = props.lit.to_bool(); + let is_receiving_power = block_receives_redstone_power(world, *block_pos).await; + + if is_lit && !is_receiving_power { + props.lit = props.lit.flip(); + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_LISTENERS, + ) + .await; + } + } +} diff --git a/pumpkin/src/block/blocks/redstone/redstone_torch.rs b/pumpkin/src/block/blocks/redstone/redstone_torch.rs new file mode 100644 index 000000000..ef60d59a9 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/redstone_torch.rs @@ -0,0 +1,239 @@ +use async_trait::async_trait; +use pumpkin_data::block::Block; +use pumpkin_data::block::BlockState; +use pumpkin_data::block::HorizontalFacing; +use pumpkin_data::block::{BlockProperties, Boolean}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::BlockDirection; +use pumpkin_world::block::HorizontalFacingExt; +use pumpkin_world::chunk::TickPriority; + +type RWallTorchProps = pumpkin_data::block::FurnaceLikeProperties; +type RTorchProps = pumpkin_data::block::RedstoneOreLikeProperties; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::block::registry::BlockRegistry; +use crate::server::Server; +use crate::world::BlockFlags; +use crate::world::World; + +use super::get_redstone_power; + +#[allow(clippy::too_many_lines)] +pub fn register_redstone_torch_blocks(manager: &mut BlockRegistry) { + for block in ["redstone_torch", "redstone_wall_torch"] { + pub struct TorchBlock { + id: &'static str, + } + impl BlockMetadata for TorchBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn id(&self) -> &'static str { + self.id + } + } + + #[async_trait] + impl PumpkinBlock for TorchBlock { + 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 { + if face.is_horizontal() { + let mut torch_props = RWallTorchProps::default(&Block::REDSTONE_WALL_TORCH); + torch_props.facing = face.to_horizontal_facing().unwrap().opposite(); + torch_props.lit = + Boolean::from_bool(should_be_lit(world, *block_pos, *face).await); + return torch_props.to_state_id(&Block::REDSTONE_WALL_TORCH); + } + let mut torch_props = RTorchProps::default(&Block::REDSTONE_TORCH); + torch_props.lit = Boolean::from_bool( + should_be_lit(world, *block_pos, BlockDirection::Down).await, + ); + return torch_props.to_state_id(&Block::REDSTONE_TORCH); + } + + async fn on_neighbor_update( + &self, + world: &World, + block: &Block, + block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, + ) { + let state = world.get_block_state(block_pos).await.unwrap(); + + if world.is_block_tick_scheduled(block_pos, block).await { + return; + } + + if *block == Block::REDSTONE_WALL_TORCH { + let props = RWallTorchProps::from_state_id(state.id, block); + if props.lit.to_bool() + != should_be_lit( + world, + *block_pos, + props.facing.to_block_direction().opposite(), + ) + .await + { + world + .schedule_block_tick(block, *block_pos, 2, TickPriority::Normal) + .await; + } + } else if *block == Block::REDSTONE_TORCH { + let props = RTorchProps::from_state_id(state.id, block); + if props.lit.to_bool() + != should_be_lit(world, *block_pos, BlockDirection::Down).await + { + world + .schedule_block_tick(block, *block_pos, 2, TickPriority::Normal) + .await; + } + } + } + + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: &BlockDirection, + ) -> bool { + true + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + if *block == Block::REDSTONE_WALL_TORCH { + let props = RWallTorchProps::from_state_id(state.id, block); + if props.lit.to_bool() && *direction != props.facing.to_block_direction() { + return 15; + } + } else if *block == Block::REDSTONE_TORCH { + let props = RTorchProps::from_state_id(state.id, block); + if props.lit.to_bool() && *direction != BlockDirection::Up { + return 15; + } + } + 0 + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + if *direction == BlockDirection::Down { + if *block == Block::REDSTONE_WALL_TORCH { + let props = RWallTorchProps::from_state_id(state.id, block); + if props.lit.to_bool() { + return 15; + } + } else if *block == Block::REDSTONE_TORCH { + let props = RTorchProps::from_state_id(state.id, block); + if props.lit.to_bool() { + return 15; + } + } + } + 0 + } + + async fn on_scheduled_tick(&self, world: &World, block: &Block, block_pos: &BlockPos) { + let state = world.get_block_state(block_pos).await.unwrap(); + if *block == Block::REDSTONE_WALL_TORCH { + let mut props = RWallTorchProps::from_state_id(state.id, block); + let should_be_lit_now = should_be_lit( + world, + *block_pos, + props.facing.to_block_direction().opposite(), + ) + .await; + if props.lit.to_bool() != should_be_lit_now { + props.lit = Boolean::from_bool(should_be_lit_now); + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_ALL, + ) + .await; + update_neighbors(world, *block_pos).await; + } + } else if *block == Block::REDSTONE_TORCH { + let mut props = RTorchProps::from_state_id(state.id, block); + let should_be_lit_now = + should_be_lit(world, *block_pos, BlockDirection::Down).await; + if props.lit.to_bool() != should_be_lit_now { + props.lit = Boolean::from_bool(should_be_lit_now); + world + .set_block_state( + block_pos, + props.to_state_id(block), + BlockFlags::NOTIFY_ALL, + ) + .await; + update_neighbors(world, *block_pos).await; + } + } + } + + async fn placed( + &self, + world: &World, + _block: &Block, + _state_id: u16, + block_pos: &BlockPos, + _old_state_id: u16, + _notify: bool, + ) { + update_neighbors(world, *block_pos).await; + } + + async fn on_state_replaced( + &self, + world: &World, + _block: &Block, + location: BlockPos, + _old_state_id: u16, + _moved: bool, + ) { + update_neighbors(world, location).await; + } + } + + manager.register(TorchBlock { id: block }); + } +} + +pub async fn should_be_lit(world: &World, pos: BlockPos, face: BlockDirection) -> bool { + let other_pos = pos.offset(face.to_offset()); + let (block, state) = world.get_block_and_block_state(&other_pos).await.unwrap(); + get_redstone_power(&block, &state, world, other_pos, face).await == 0 +} + +pub async fn update_neighbors(world: &World, pos: BlockPos) { + for dir in BlockDirection::all() { + let other_pos = pos.offset(dir.to_offset()); + world.update_neighbors(&other_pos, None).await; + } +} diff --git a/pumpkin/src/block/blocks/redstone/redstone_wire.rs b/pumpkin/src/block/blocks/redstone/redstone_wire.rs new file mode 100644 index 000000000..f87894f19 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/redstone_wire.rs @@ -0,0 +1,618 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use pumpkin_data::block::{ + Block, BlockState, EastWireConnection, EnumVariants, Integer0To15, NorthWireConnection, + ObserverLikeProperties, RedstoneWireLikeProperties, RepeaterLikeProperties, + SouthWireConnection, WestWireConnection, +}; +use pumpkin_data::block::{BlockProperties, HorizontalFacing}; +use pumpkin_data::item::Item; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::{BlockDirection, HorizontalFacingExt}; + +use crate::block::registry::BlockActionResult; +use crate::entity::player::Player; +use crate::world::BlockFlags; +use crate::{block::pumpkin_block::PumpkinBlock, server::Server, world::World}; + +use super::turbo::RedstoneWireTurbo; +use super::{get_redstone_power_no_dust, update_wire_neighbors}; + +type RedstoneWireProperties = RedstoneWireLikeProperties; + +#[pumpkin_block("minecraft:redstone_wire")] +pub struct RedstoneWireBlock; + +#[async_trait] +impl PumpkinBlock for RedstoneWireBlock { + // Start of placement + + async fn can_place_at(&self, world: &World, block_pos: &BlockPos) -> bool { + let floor = world.get_block_state(&block_pos.down()).await.unwrap(); + // TODO: Only check face instead of block + return floor.is_solid; + } + + 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 mut wire = RedstoneWireProperties::default(block); + wire.power = Integer0To15::from_index(calculate_power(world, *block_pos).await.into()); + wire = get_regulated_sides(wire, world, *block_pos).await; + if is_dot(wire) { + wire = make_cross(wire.power); + } + + wire.to_state_id(block) + } + + async fn get_state_for_neighbor_update( + &self, + world: &World, + block: &Block, + state: u16, + block_pos: &BlockPos, + direction: &BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: u16, + ) -> u16 { + let mut wire = RedstoneWireProperties::from_state_id(state, block); + let old_state = wire; + let new_side: WireConnection; + + match direction { + BlockDirection::Up => { + return state; + } + BlockDirection::Down => { + return get_regulated_sides(wire, world, *block_pos) + .await + .to_state_id(block); + } + BlockDirection::North => { + let side = get_side(world, *block_pos, BlockDirection::North).await; + wire.north = side.to_north(); + new_side = side; + } + BlockDirection::South => { + let side = get_side(world, *block_pos, BlockDirection::South).await; + wire.south = side.to_south(); + new_side = side; + } + BlockDirection::East => { + let side = get_side(world, *block_pos, BlockDirection::East).await; + wire.east = side.to_east(); + new_side = side; + } + BlockDirection::West => { + let side = get_side(world, *block_pos, BlockDirection::West).await; + wire.west = side.to_west(); + new_side = side; + } + } + + wire = get_regulated_sides(wire, world, *block_pos).await; + if is_cross(old_state) && new_side.is_none() { + return wire.to_state_id(block); + } + if !is_dot(old_state) && is_dot(wire) { + let power = wire.power; + wire = make_cross(power); + } + wire.to_state_id(block) + } + + async fn prepare( + &self, + world: &World, + block_pos: &BlockPos, + _block: &Block, + state_id: u16, + flags: BlockFlags, + ) { + let wire_props = RedstoneWireLikeProperties::from_state_id(state_id, &Block::REDSTONE_WIRE); + + for direction in BlockDirection::horizontal() { + let other_block_pos = block_pos.offset(direction.to_offset()); + let other_block = world.get_block(&other_block_pos).await.unwrap(); + + if wire_props.is_side_connected(direction) && other_block != Block::REDSTONE_WIRE { + let up_block_pos = other_block_pos.up(); + let up_block = world.get_block(&up_block_pos).await.unwrap(); + if up_block == Block::REDSTONE_WIRE { + world + .replace_with_state_for_neighbor_update( + &up_block_pos, + &direction.opposite(), + flags, + ) + .await; + } + + let down_block_pos = other_block_pos.down(); + let down_block = world.get_block(&down_block_pos).await.unwrap(); + if down_block == Block::REDSTONE_WIRE { + world + .replace_with_state_for_neighbor_update( + &down_block_pos, + &direction.opposite(), + flags, + ) + .await; + } + } + } + } + + async fn normal_use( + &self, + block: &Block, + _player: &Player, + location: BlockPos, + _server: &Server, + world: &World, + ) { + let state = world.get_block_state(&location).await.unwrap(); + let wire = RedstoneWireProperties::from_state_id(state.id, block); + on_use(wire, world, location).await; + } + + async fn use_with_item( + &self, + block: &Block, + _player: &Player, + location: BlockPos, + _item: &Item, + _server: &Server, + world: &World, + ) -> BlockActionResult { + let state = world.get_block_state(&location).await.unwrap(); + let wire = RedstoneWireProperties::from_state_id(state.id, block); + if on_use(wire, world, location).await { + BlockActionResult::Consume + } else { + BlockActionResult::Continue + } + } + + async fn on_neighbor_update( + &self, + world: &World, + block: &Block, + block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, + ) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut wire = RedstoneWireProperties::from_state_id(state.id, block); + let new_power = calculate_power(world, *block_pos).await; + if wire.power.to_index() as u8 != new_power { + wire.power = Integer0To15::from_index(new_power.into()); + world + .set_block_state( + block_pos, + wire.to_state_id(&Block::REDSTONE_WIRE), + BlockFlags::empty(), + ) + .await; + RedstoneWireTurbo::update_surrounding_neighbors(world, *block_pos).await; + } + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let wire = RedstoneWireProperties::from_state_id(state.id, block); + if *direction == BlockDirection::Up || wire.is_side_connected(direction.opposite()) { + wire.power.to_index() as u8 + } else { + 0 + } + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let wire = RedstoneWireProperties::from_state_id(state.id, block); + if *direction == BlockDirection::Up || wire.is_side_connected(direction.opposite()) { + wire.power.to_index() as u8 + } else { + 0 + } + } + + async fn placed( + &self, + world: &World, + _block: &Block, + _state_id: u16, + block_pos: &BlockPos, + _old_state_id: u16, + _notify: bool, + ) { + update_wire_neighbors(world, *block_pos).await; + } + + async fn broken( + &self, + _block: &Block, + _player: &Player, + location: BlockPos, + _server: &Server, + world: Arc, + _state: BlockState, + ) { + update_wire_neighbors(&world, location).await; + } +} + +async fn on_use(wire: RedstoneWireProperties, world: &World, block_pos: BlockPos) -> bool { + if is_cross(wire) || is_dot(wire) { + let mut new_wire = if is_cross(wire) { + RedstoneWireProperties::default(&Block::REDSTONE_WIRE) + } else { + make_cross(wire.power) + }; + new_wire.power = wire.power; + + new_wire = get_regulated_sides(new_wire, world, block_pos).await; + if wire != new_wire { + world + .set_block_state( + &block_pos, + new_wire.to_state_id(&Block::REDSTONE_WIRE), + BlockFlags::empty(), + ) + .await; + update_wire_neighbors(world, block_pos).await; + return true; + } + } + false +} + +pub fn make_cross(power: Integer0To15) -> RedstoneWireProperties { + RedstoneWireProperties { + north: NorthWireConnection::Side, + south: SouthWireConnection::Side, + east: EastWireConnection::Side, + west: WestWireConnection::Side, + power, + } +} + +async fn can_connect_to( + world: &World, + block: &Block, + side: BlockDirection, + state: &BlockState, +) -> bool { + if world + .block_registry + .emits_redstone_power(block, state, &side) + .await + { + return true; + } + if *block == Block::REPEATER { + let repeater_props = RepeaterLikeProperties::from_state_id(state.id, block); + return repeater_props.facing.to_block_direction() == side + || repeater_props.facing.to_block_direction() == side.opposite(); + } else if *block == Block::OBSERVER { + let observer_props = ObserverLikeProperties::from_state_id(state.id, block); + return observer_props.facing == side.to_facing(); + } else if *block == Block::REDSTONE_WIRE { + return true; + } + false +} + +fn can_connect_diagonal_to(block: &Block) -> bool { + *block == Block::REDSTONE_WIRE +} + +pub async fn get_side(world: &World, pos: BlockPos, side: BlockDirection) -> WireConnection { + let neighbor_pos: BlockPos = pos.offset(side.to_offset()); + let (neighbor, state) = world + .get_block_and_block_state(&neighbor_pos) + .await + .unwrap(); + + if can_connect_to(world, &neighbor, side, &state).await { + return WireConnection::Side; + } + + let up_pos = pos.offset(BlockDirection::Up.to_offset()); + let up_state = world.get_block_state(&up_pos).await.unwrap(); + + if !up_state.is_solid + && can_connect_diagonal_to( + &world + .get_block(&neighbor_pos.offset(BlockDirection::Up.to_offset())) + .await + .unwrap(), + ) + { + WireConnection::Up + } else if !state.is_solid + && can_connect_diagonal_to( + &world + .get_block(&neighbor_pos.offset(BlockDirection::Down.to_offset())) + .await + .unwrap(), + ) + { + WireConnection::Side + } else { + WireConnection::None + } +} + +async fn get_all_sides( + mut wire: RedstoneWireProperties, + world: &World, + pos: BlockPos, +) -> RedstoneWireProperties { + wire.north = get_side(world, pos, BlockDirection::North).await.to_north(); + wire.south = get_side(world, pos, BlockDirection::South).await.to_south(); + wire.east = get_side(world, pos, BlockDirection::East).await.to_east(); + wire.west = get_side(world, pos, BlockDirection::West).await.to_west(); + wire +} + +pub fn is_dot(wire: RedstoneWireProperties) -> bool { + wire.north == NorthWireConnection::None + && wire.south == SouthWireConnection::None + && wire.east == EastWireConnection::None + && wire.west == WestWireConnection::None +} + +pub fn is_cross(wire: RedstoneWireProperties) -> bool { + wire.north == NorthWireConnection::Side + && wire.south == SouthWireConnection::Side + && wire.east == EastWireConnection::Side + && wire.west == WestWireConnection::Side +} + +pub async fn get_regulated_sides( + wire: RedstoneWireProperties, + world: &World, + pos: BlockPos, +) -> RedstoneWireProperties { + let mut state = get_all_sides(wire, world, pos).await; + if is_dot(wire) && is_dot(state) { + return state; + } + let north_none = state.north.is_none(); + let south_none = state.south.is_none(); + let east_none = state.east.is_none(); + let west_none = state.west.is_none(); + let north_south_none = north_none && south_none; + let east_west_none = east_none && west_none; + if north_none && east_west_none { + state.north = NorthWireConnection::Side; + } + if south_none && east_west_none { + state.south = SouthWireConnection::Side; + } + if east_none && north_south_none { + state.east = EastWireConnection::Side; + } + if west_none && north_south_none { + state.west = WestWireConnection::Side; + } + state +} + +trait RedstoneWireLikePropertiesExt { + fn is_side_connected(&self, direction: BlockDirection) -> bool; + //fn get_connection_type(&self, direction: BlockDirection) -> WireConnection; +} + +impl RedstoneWireLikePropertiesExt for RedstoneWireLikeProperties { + fn is_side_connected(&self, direction: BlockDirection) -> bool { + match direction { + BlockDirection::North => self.north.to_wire_connection().is_connected(), + BlockDirection::South => self.south.to_wire_connection().is_connected(), + BlockDirection::East => self.east.to_wire_connection().is_connected(), + BlockDirection::West => self.west.to_wire_connection().is_connected(), + _ => false, + } + } + + /* + fn get_connection_type(&self, direction: BlockDirection) -> WireConnection { + match direction { + BlockDirection::North => self.north.to_wire_connection(), + BlockDirection::South => self.south.to_wire_connection(), + BlockDirection::East => self.east.to_wire_connection(), + BlockDirection::West => self.west.to_wire_connection(), + _ => WireConnection::None, + } + } + */ +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum WireConnection { + Up, + Side, + None, +} + +impl WireConnection { + fn is_connected(self) -> bool { + self != Self::None + } + + fn is_none(self) -> bool { + self == Self::None + } + + fn to_north(self) -> NorthWireConnection { + match self { + Self::Up => NorthWireConnection::Up, + Self::Side => NorthWireConnection::Side, + Self::None => NorthWireConnection::None, + } + } + + fn to_south(self) -> SouthWireConnection { + match self { + Self::Up => SouthWireConnection::Up, + Self::Side => SouthWireConnection::Side, + Self::None => SouthWireConnection::None, + } + } + + fn to_east(self) -> EastWireConnection { + match self { + Self::Up => EastWireConnection::Up, + Self::Side => EastWireConnection::Side, + Self::None => EastWireConnection::None, + } + } + + fn to_west(self) -> WestWireConnection { + match self { + Self::Up => WestWireConnection::Up, + Self::Side => WestWireConnection::Side, + Self::None => WestWireConnection::None, + } + } +} +trait CardinalWireConnectionExt { + fn to_wire_connection(&self) -> WireConnection; + fn is_none(&self) -> bool; +} + +impl CardinalWireConnectionExt for NorthWireConnection { + fn to_wire_connection(&self) -> WireConnection { + match self { + Self::Side => WireConnection::Side, + Self::Up => WireConnection::Up, + Self::None => WireConnection::None, + } + } + + fn is_none(&self) -> bool { + *self == Self::None + } +} + +impl CardinalWireConnectionExt for SouthWireConnection { + fn to_wire_connection(&self) -> WireConnection { + match self { + Self::Side => WireConnection::Side, + Self::Up => WireConnection::Up, + Self::None => WireConnection::None, + } + } + + fn is_none(&self) -> bool { + *self == Self::None + } +} + +impl CardinalWireConnectionExt for EastWireConnection { + fn to_wire_connection(&self) -> WireConnection { + match self { + Self::Side => WireConnection::Side, + Self::Up => WireConnection::Up, + Self::None => WireConnection::None, + } + } + + fn is_none(&self) -> bool { + *self == Self::None + } +} + +impl CardinalWireConnectionExt for WestWireConnection { + fn to_wire_connection(&self) -> WireConnection { + match self { + Self::Side => WireConnection::Side, + Self::Up => WireConnection::Up, + Self::None => WireConnection::None, + } + } + + fn is_none(&self) -> bool { + *self == Self::None + } +} + +async fn max_wire_power(wire_power: u8, world: &World, pos: BlockPos) -> u8 { + let (block, block_state) = world.get_block_and_block_state(&pos).await.unwrap(); + if block == Block::REDSTONE_WIRE { + let wire = RedstoneWireProperties::from_state_id(block_state.id, &block); + wire_power.max(wire.power.to_index() as u8) + } else { + wire_power + } +} + +async fn calculate_power(world: &World, pos: BlockPos) -> u8 { + let mut block_power: u8 = 0; + let mut wire_power: u8 = 0; + + let up_pos = pos.offset(BlockDirection::Up.to_offset()); + let (_up_block, up_state) = world.get_block_and_block_state(&up_pos).await.unwrap(); + + for side in &BlockDirection::all() { + let neighbor_pos = pos.offset(side.to_offset()); + wire_power = max_wire_power(wire_power, world, neighbor_pos).await; + let (neighbor, neighbor_state) = world + .get_block_and_block_state(&neighbor_pos) + .await + .unwrap(); + block_power = block_power.max( + get_redstone_power_no_dust(&neighbor, &neighbor_state, world, neighbor_pos, *side) + .await, + ); + if side.is_horizontal() { + if !up_state.is_solid + /*TODO: && !neighbor.is_transparent() */ + { + wire_power = max_wire_power( + wire_power, + world, + neighbor_pos.offset(BlockDirection::Up.to_offset()), + ) + .await; + } + + if !neighbor_state.is_solid { + wire_power = max_wire_power( + wire_power, + world, + neighbor_pos.offset(BlockDirection::Down.to_offset()), + ) + .await; + } + } + } + + block_power.max(wire_power.saturating_sub(1)) +} diff --git a/pumpkin/src/block/blocks/redstone/repeater.rs b/pumpkin/src/block/blocks/redstone/repeater.rs new file mode 100644 index 000000000..ee3aee99b --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/repeater.rs @@ -0,0 +1,257 @@ +use async_trait::async_trait; +use pumpkin_data::{ + block::{ + Block, BlockProperties, BlockState, Boolean, EnumVariants, HorizontalFacing, Integer1To4, + }, + item::Item, +}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::{ + block::{BlockDirection, HorizontalFacingExt}, + chunk::TickPriority, +}; + +use crate::{ + block::{pumpkin_block::PumpkinBlock, registry::BlockActionResult}, + entity::player::Player, + server::Server, + world::{BlockFlags, World}, +}; + +use super::{diode_get_input_strength, get_weak_power, is_diode}; + +type RepeaterProperties = pumpkin_data::block::RepeaterLikeProperties; + +#[pumpkin_block("minecraft:repeater")] +pub struct RepeaterBlock; + +#[async_trait] +impl PumpkinBlock for RepeaterBlock { + 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 mut props = RepeaterProperties::default(block); + props.facing = player_direction.opposite(); + props.locked = + Boolean::from_bool(should_be_locked(*player_direction, world, *block_pos).await); + props.to_state_id(block) + } + + async fn on_neighbor_update( + &self, + world: &World, + block: &Block, + block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, + ) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut rep = RepeaterProperties::from_state_id(state.id, block); + let should_be_locked = should_be_locked(rep.facing, world, *block_pos).await; + if !rep.locked.to_bool() && should_be_locked { + rep.locked = Boolean::True; + world + .set_block_state(block_pos, rep.to_state_id(block), BlockFlags::empty()) + .await; + } else if rep.locked.to_bool() && !should_be_locked { + rep.locked = Boolean::False; + world + .set_block_state(block_pos, rep.to_state_id(block), BlockFlags::empty()) + .await; + } + + if !rep.locked.to_bool() && !world.is_block_tick_scheduled(block_pos, block).await { + let should_be_powered = should_be_powered(rep, world, *block_pos).await; + if should_be_powered != rep.powered.to_bool() { + schedule_tick(rep, world, *block_pos, should_be_powered).await; + } + } + } + + async fn on_scheduled_tick(&self, world: &World, block: &Block, block_pos: &BlockPos) { + let state = world.get_block_state(block_pos).await.unwrap(); + let mut rep = RepeaterProperties::from_state_id(state.id, block); + if rep.locked.to_bool() { + return; + } + + let should_be_powered = should_be_powered(rep, world, *block_pos).await; + if rep.powered.to_bool() && !should_be_powered { + rep.powered = Boolean::False; + world + .set_block_state(block_pos, rep.to_state_id(block), BlockFlags::empty()) + .await; + on_state_change(rep, world, *block_pos).await; + } else if !rep.powered.to_bool() { + rep.powered = Boolean::True; + world + .set_block_state(block_pos, rep.to_state_id(block), BlockFlags::empty()) + .await; + on_state_change(rep, world, *block_pos).await; + } + } + + async fn normal_use( + &self, + block: &Block, + _player: &Player, + location: BlockPos, + _server: &Server, + world: &World, + ) { + let state = world.get_block_state(&location).await.unwrap(); + let props = RepeaterProperties::from_state_id(state.id, block); + on_use(props, world, location, block).await; + } + + async fn use_with_item( + &self, + block: &Block, + _player: &Player, + location: BlockPos, + _item: &Item, + _server: &Server, + world: &World, + ) -> BlockActionResult { + let state = world.get_block_state(&location).await.unwrap(); + let props = RepeaterProperties::from_state_id(state.id, block); + on_use(props, world, location, block).await; + BlockActionResult::Consume + } + + async fn get_weak_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let repeater_props = RepeaterProperties::from_state_id(state.id, block); + if repeater_props.facing.to_block_direction() == *direction + && repeater_props.powered.to_bool() + { + return 15; + } + 0 + } + + async fn get_strong_redstone_power( + &self, + block: &Block, + _world: &World, + _block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let repeater_props = RepeaterProperties::from_state_id(state.id, block); + if repeater_props.facing.to_block_direction() == *direction + && repeater_props.powered.to_bool() + { + return 15; + } + 0 + } + + async fn emits_redstone_power( + &self, + block: &Block, + state: &BlockState, + direction: &BlockDirection, + ) -> bool { + let repeater_props = RepeaterProperties::from_state_id(state.id, block); + repeater_props.facing.to_block_direction() == *direction + || repeater_props.facing.to_block_direction() == direction.opposite() + } +} + +async fn on_use(props: RepeaterProperties, world: &World, block_pos: BlockPos, block: &Block) { + let mut props = props; + props.delay = match props.delay { + Integer1To4::L1 => Integer1To4::L2, + Integer1To4::L2 => Integer1To4::L3, + Integer1To4::L3 => Integer1To4::L4, + Integer1To4::L4 => Integer1To4::L1, + }; + let state = props.to_state_id(block); + world + .set_block_state(&block_pos, state, BlockFlags::empty()) + .await; +} + +async fn should_be_locked(facing: HorizontalFacing, world: &World, pos: BlockPos) -> bool { + let right_side = get_power_on_side(world, pos, facing.rotate()).await; + let left_side = get_power_on_side(world, pos, facing.rotate_ccw()).await; + std::cmp::max(right_side, left_side) > 0 +} + +async fn get_power_on_side(world: &World, pos: BlockPos, side: HorizontalFacing) -> u8 { + let side_pos = pos.offset(side.to_block_direction().to_offset()); + let (side_block, side_state) = world.get_block_and_block_state(&side_pos).await.unwrap(); + if is_diode(&side_block) { + get_weak_power( + &side_block, + &side_state, + world, + side_pos, + side.to_block_direction(), + false, + ) + .await + } else { + 0 + } +} + +async fn on_state_change(rep: RepeaterProperties, world: &World, pos: BlockPos) { + let front_pos = pos.offset(rep.facing.opposite().to_block_direction().to_offset()); + let front_block = world.get_block(&front_pos).await.unwrap(); + world.update_neighbor(&front_pos, &front_block).await; + for direction in &BlockDirection::all() { + let neighbor_pos = front_pos.offset(direction.to_offset()); + let block = world.get_block(&neighbor_pos).await.unwrap(); + world.update_neighbor(&neighbor_pos, &block).await; + } +} + +async fn schedule_tick( + rep: RepeaterProperties, + world: &World, + pos: BlockPos, + should_be_powered: bool, +) { + let front_block = world + .get_block(&pos.offset(rep.facing.opposite().to_block_direction().to_offset())) + .await + .unwrap(); + let priority = if is_diode(&front_block) { + TickPriority::ExtremelyHigh + } else if !should_be_powered { + TickPriority::VeryHigh + } else { + TickPriority::High + }; + world + .schedule_block_tick( + &Block::REPEATER, + pos, + // 1 redstone tick = 2 ticks + (rep.delay.to_index() + 1) * 2, + priority, + ) + .await; +} + +async fn should_be_powered(rep: RepeaterProperties, world: &World, pos: BlockPos) -> bool { + diode_get_input_strength(world, pos, rep.facing.to_block_direction()).await > 0 +} diff --git a/pumpkin/src/block/blocks/redstone/target_block.rs b/pumpkin/src/block/blocks/redstone/target_block.rs new file mode 100644 index 000000000..0eec0f69a --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/target_block.rs @@ -0,0 +1,22 @@ +use async_trait::async_trait; +use pumpkin_data::block::{Block, BlockState}; +use pumpkin_macros::pumpkin_block; + +use pumpkin_world::block::BlockDirection; + +use crate::block::pumpkin_block::PumpkinBlock; + +#[pumpkin_block("minecraft:target")] +pub struct TargetBlock; + +#[async_trait] +impl PumpkinBlock for TargetBlock { + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: &BlockDirection, + ) -> bool { + true + } +} diff --git a/pumpkin/src/block/blocks/redstone/turbo.rs b/pumpkin/src/block/blocks/redstone/turbo.rs new file mode 100644 index 000000000..6afa4eb32 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/turbo.rs @@ -0,0 +1,419 @@ +//! The implementation of "Redstone Wire Turbo" was largely based on +//! the accelerator created by theosib. For more information, see: +//! . + +use pumpkin_data::block::{ + Block, BlockProperties, BlockState, EnumVariants, Integer0To15, RedstoneWireLikeProperties, +}; +use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; +use pumpkin_world::block::BlockDirection; +use rustc_hash::FxHashMap; + +use crate::world::{BlockFlags, World}; + +use super::get_redstone_power_no_dust; + +type RedstoneWireProps = RedstoneWireLikeProperties; + +fn unwrap_wire(state: &BlockState) -> RedstoneWireProps { + RedstoneWireProps::from_state_id(state.id, &Block::REDSTONE_WIRE) +} + +#[derive(Clone, Copy)] +struct NodeId { + index: usize, +} + +struct UpdateNode { + pos: BlockPos, + /// The cached state of the block + state: BlockState, + /// This will only be `Some` when all the neighbors are identified. + neighbors: Option>, + visited: bool, + xbias: i32, + zbias: i32, + layer: u32, +} + +impl UpdateNode { + async fn new(world: &World, pos: BlockPos) -> Self { + Self { + pos, + state: world.get_block_state(&pos).await.unwrap(), + visited: false, + neighbors: None, + xbias: 0, + zbias: 0, + layer: 0, + } + } +} + +pub(super) struct RedstoneWireTurbo { + nodes: Vec, + node_cache: FxHashMap, + update_queue: Vec>, + current_walk_layer: u32, +} + +impl RedstoneWireTurbo { + // Internal numbering for cardinal directions + const NORTH: usize = 0; + const EAST: usize = 1; + const SOUTH: usize = 2; + const WEST: usize = 3; + + pub fn new() -> Self { + Self { + nodes: Vec::new(), + node_cache: FxHashMap::default(), + update_queue: vec![vec![], vec![], vec![]], + current_walk_layer: 0, + } + } + + fn get_node(&self, node_id: NodeId) -> &UpdateNode { + &self.nodes[node_id.index] + } + + fn compute_all_neighbors(pos: BlockPos) -> [BlockPos; 24] { + let Vector3 { x, y, z } = pos.0; + [ + BlockPos::new(x - 1, y, z), + BlockPos::new(x + 1, y, z), + BlockPos::new(x, y - 1, z), + BlockPos::new(x, y + 1, z), + BlockPos::new(x, y, z - 1), + BlockPos::new(x, y, z + 1), + // Neighbors of neighbors, in the same order, + // except that duplicates are not included + BlockPos::new(x - 2, y, z), + BlockPos::new(x - 1, y - 1, z), + BlockPos::new(x - 1, y + 1, z), + BlockPos::new(x - 1, y, z - 1), + BlockPos::new(x - 1, y, z + 1), + BlockPos::new(x + 2, y, z), + BlockPos::new(x + 1, y - 1, z), + BlockPos::new(x + 1, y + 1, z), + BlockPos::new(x + 1, y, z - 1), + BlockPos::new(x + 1, y, z + 1), + BlockPos::new(x, y - 2, z), + BlockPos::new(x, y - 1, z - 1), + BlockPos::new(x, y - 1, z + 1), + BlockPos::new(x, y + 2, z), + BlockPos::new(x, y + 1, z - 1), + BlockPos::new(x, y + 1, z + 1), + BlockPos::new(x, y, z - 2), + BlockPos::new(x, y, z + 2), + ] + } + + fn compute_heading(rx: i32, rz: i32) -> usize { + let code = (rx + 1) + 3 * (rz + 1); + match code { + 0 | 1 => Self::NORTH, + 2 | 5 => Self::EAST, + 3 | 4 => Self::WEST, + 6..=8 => Self::SOUTH, + _ => unreachable!(), + } + } + + // const UPDATE_REDSTONE: [bool; 24] = [ + // true, true, false, false, true, true, // 0 to 5 + // false, true, true, false, false, false, // 6 to 11 + // true, true, false, false, false, true, // 12 to 17 + // true, false, true, true, false, false // 18 to 23 + // ]; + + async fn identify_neighbors(&mut self, world: &World, upd1: NodeId) { + let pos = self.nodes[upd1.index].pos; + let neighbors = Self::compute_all_neighbors(pos); + let mut neighbors_visited = Vec::with_capacity(24); + let mut neighbor_nodes = Vec::with_capacity(24); + + for neighbor_pos in &neighbors[0..24] { + let neighbor = if self.node_cache.contains_key(neighbor_pos) { + self.node_cache[neighbor_pos] + } else { + let node_id = NodeId { + index: self.nodes.len(), + }; + self.node_cache.insert(*neighbor_pos, node_id); + self.nodes.push(UpdateNode::new(world, *neighbor_pos).await); + node_id + }; + + let node = &self.nodes[neighbor.index]; + // if let Block::RedstoneWire { .. } = node.state { + // if RedstoneWireTurbo::UPDATE_REDSTONE[i] { + neighbor_nodes.push(neighbor); + neighbors_visited.push(node.visited); + // continue; + // } + // } + // neighbor_nodes.push(None); + // neighbors_visited.push(false); + } + + let from_west = neighbors_visited[0] || neighbors_visited[7] || neighbors_visited[8]; + let from_east = neighbors_visited[1] || neighbors_visited[12] || neighbors_visited[13]; + let from_north = neighbors_visited[4] || neighbors_visited[17] || neighbors_visited[20]; + let from_south = neighbors_visited[5] || neighbors_visited[18] || neighbors_visited[21]; + + let mut cx = 0; + let mut cz = 0; + if from_west { + cx += 1; + }; + if from_east { + cx -= 1; + }; + if from_north { + cz += 1; + }; + if from_south { + cz -= 1; + }; + + let UpdateNode { xbias, zbias, .. } = &self.nodes[upd1.index]; + let xbias = *xbias; + let zbias = *zbias; + + let heading; + if cx == 0 && cz == 0 { + heading = Self::compute_heading(xbias, zbias); + + for node_id in &neighbor_nodes { + // if let Some(node_id) = node_id { + let nn = &mut self.nodes[node_id.index]; + nn.xbias = xbias; + nn.zbias = zbias; + // } + } + } else { + if cx != 0 && cz != 0 { + if xbias != 0 { + cz = 0; + } + if zbias != 0 { + cx = 0; + } + } + heading = Self::compute_heading(cx, cz); + + for node_id in &neighbor_nodes { + // if let Some(node_id) = node_id { + let nn = &mut self.nodes[node_id.index]; + nn.xbias = cx; + nn.zbias = cz; + // } + } + } + + self.orient_neighbors(&neighbor_nodes, upd1, heading); + } + + const REORDING: [[usize; 24]; 4] = [ + [ + 2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15, + ], + [ + 2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10, + ], + [ + 2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9, + ], + [ + 2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14, + ], + ]; + + fn orient_neighbors(&mut self, src: &[NodeId], dst_id: NodeId, heading: usize) { + let dst = &mut self.nodes[dst_id.index]; + let mut neighbors = Vec::with_capacity(24); + let re = Self::REORDING[heading]; + for i in &re { + neighbors.push(src[*i]); + } + dst.neighbors = Some(neighbors); + } + + /// This is the start of a great adventure + pub async fn update_surrounding_neighbors(world: &World, pos: BlockPos) { + let mut turbo = Self::new(); + let mut root_node = UpdateNode::new(world, pos).await; + root_node.visited = true; + let node_id = NodeId { index: 0 }; + turbo.node_cache.insert(pos, node_id); + turbo.nodes.push(root_node); + turbo.propagate_changes(world, node_id, 0).await; + turbo.breadth_first_walk(world).await; + } + + async fn propagate_changes(&mut self, world: &World, upd1: NodeId, layer: u32) { + if self.nodes[upd1.index].neighbors.is_none() { + self.identify_neighbors(world, upd1).await; + } + + let neighbors: [NodeId; 24] = self.nodes[upd1.index].neighbors.as_ref().unwrap()[0..24] + .try_into() + .unwrap(); + + let layer1 = layer + 1; + + for neighbor_id in neighbors { + let neighbor = &mut self.nodes[neighbor_id.index]; + if layer1 > neighbor.layer { + neighbor.layer = layer1; + self.update_queue[1].push(neighbor_id); + } + } + + let layer2 = layer + 2; + + for neighbor_id in &neighbors[0..4] { + let neighbor = &mut self.nodes[neighbor_id.index]; + if layer2 > neighbor.layer { + neighbor.layer = layer2; + self.update_queue[2].push(*neighbor_id); + } + } + } + + async fn breadth_first_walk(&mut self, world: &World) { + self.shift_queue(); + self.current_walk_layer = 1; + + while !self.update_queue[0].is_empty() || !self.update_queue[1].is_empty() { + for node_id in self.update_queue[0].clone() { + let block = &Block::from_state_id(self.nodes[node_id.index].state.id).unwrap(); + if *block == Block::REDSTONE_WIRE { + self.update_node(world, node_id, self.current_walk_layer) + .await; + } else { + // This only works because updating any other block than a wire will + // never change the state of the block. If that changes in the future, + // the cached state will need to be updated + world + .update_neighbor(&self.nodes[node_id.index].pos, block) + .await; + } + } + + self.shift_queue(); + self.current_walk_layer += 1; + } + + self.current_walk_layer = 0; + } + + fn shift_queue(&mut self) { + let mut t = self.update_queue.remove(0); + t.clear(); + self.update_queue.push(t); + } + + async fn update_node(&mut self, world: &World, upd1: NodeId, layer: u32) { + let old_wire = { + let node = &mut self.nodes[upd1.index]; + node.visited = true; + unwrap_wire(&node.state) + }; + + let new_wire = self.calculate_current_changes(world, upd1).await; + if old_wire.power != new_wire.power { + let node = &mut self.nodes[upd1.index]; + let mut wire = unwrap_wire(&node.state); + wire.power = new_wire.power; + node.state = world + .get_state_by_id(wire.to_state_id(&Block::REDSTONE_WIRE)) + .unwrap(); + + self.propagate_changes(world, upd1, layer).await; + } + } + + const RS_NEIGHBORS: [usize; 4] = [4, 5, 6, 7]; + const RS_NEIGHBORS_UP: [usize; 4] = [9, 11, 13, 15]; + const RS_NEIGHBORS_DN: [usize; 4] = [8, 10, 12, 14]; + + async fn calculate_current_changes(&mut self, world: &World, upd: NodeId) -> RedstoneWireProps { + let mut wire = unwrap_wire(&self.nodes[upd.index].state); + let i = wire.power; + let mut block_power = 0; + + if self.nodes[upd.index].neighbors.is_none() { + self.identify_neighbors(world, upd).await; + } + + let pos = self.nodes[upd.index].pos; + + let mut wire_power = 0; + for side in &BlockDirection::all() { + let neighbor_pos = pos.offset(side.to_offset()); + let neighbor = &self.nodes[self.node_cache[&neighbor_pos].index].state; + wire_power = wire_power.max( + get_redstone_power_no_dust( + &Block::from_state_id(neighbor.id).unwrap(), + neighbor, + world, + neighbor_pos, + *side, + ) + .await, + ); + } + + if wire_power < 15 { + let neighbors = self.nodes[upd.index].neighbors.as_ref().unwrap(); + + let center_up = &self.nodes[neighbors[1].index].state; + + for m in 0..4 { + let n = Self::RS_NEIGHBORS[m]; + + let neighbor_id = neighbors[n]; + let neighbor = &self.get_node(neighbor_id).state; + block_power = self.get_max_current_strength(neighbor_id, block_power); + + if !neighbor.is_solid { + let neighbor_down = neighbors[Self::RS_NEIGHBORS_DN[m]]; + block_power = self.get_max_current_strength(neighbor_down, block_power); + } else if !center_up.is_solid + /* TODO: && !neighbor.is_transparent()*/ + { + let neighbor_up = neighbors[Self::RS_NEIGHBORS_UP[m]]; + block_power = self.get_max_current_strength(neighbor_up, block_power); + } + } + } + + let mut j = block_power.saturating_sub(1); + if wire_power > j { + j = wire_power; + } + if i.to_index() as u8 != j { + wire.power = Integer0To15::from_index(j.into()); + world + .set_block_state( + &pos, + wire.to_state_id(&Block::REDSTONE_WIRE), + BlockFlags::empty(), + ) + .await; + } + wire + } + + fn get_max_current_strength(&self, upd: NodeId, strength: u8) -> u8 { + let node = &self.nodes[upd.index]; + let block = &Block::from_state_id(node.state.id).unwrap(); + if block == &Block::REDSTONE_WIRE { + (unwrap_wire(&node.state).power.to_index() as u8).max(strength) + } else { + strength + } + } +} diff --git a/pumpkin/src/block/blocks/tnt.rs b/pumpkin/src/block/blocks/tnt.rs index 3c4dcb28f..d6656758e 100644 --- a/pumpkin/src/block/blocks/tnt.rs +++ b/pumpkin/src/block/blocks/tnt.rs @@ -5,7 +5,7 @@ use crate::block::registry::BlockActionResult; use crate::entity::player::Player; use crate::entity::tnt::TNTEntity; use crate::server::Server; -use crate::world::World; +use crate::world::{BlockFlags, World}; use async_trait::async_trait; use pumpkin_data::block::Block; use pumpkin_data::entity::EntityType; @@ -37,7 +37,9 @@ impl PumpkinBlock for TNTBlock { return BlockActionResult::Continue; } let world = player.world().await; - world.set_block_state(&location, 0).await; + world + .set_block_state(&location, 0, BlockFlags::NOTIFY_ALL) + .await; let entity = world.create_entity(location.to_f64(), EntityType::TNT); let pos = entity.pos.load(); let tnt = Arc::new(TNTEntity::new(entity, DEFAULT_POWER, DEFAULT_FUSE)); diff --git a/pumpkin/src/block/blocks/torches.rs b/pumpkin/src/block/blocks/torches.rs new file mode 100644 index 000000000..444b9841a --- /dev/null +++ b/pumpkin/src/block/blocks/torches.rs @@ -0,0 +1,61 @@ +use async_trait::async_trait; +use pumpkin_data::block::Block; +use pumpkin_data::block::BlockProperties; +use pumpkin_data::block::HorizontalFacing; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::block::BlockDirection; + +type WallTorchProps = pumpkin_data::block::WallTorchLikeProperties; +// Normal tourches don't have properties + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::block::registry::BlockRegistry; +use crate::server::Server; +use crate::world::World; + +pub fn register_torch_blocks(manager: &mut BlockRegistry) { + for block in ["torch", "soul_torch"] { + pub struct TorchBlock { + id: &'static str, + } + impl BlockMetadata for TorchBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn id(&self) -> &'static str { + self.id + } + } + + #[async_trait] + impl PumpkinBlock for TorchBlock { + 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 { + if face.is_horizontal() { + let wall_block = match block.name { + "torch" => Block::WALL_TORCH, + "soul_torch" => Block::SOUL_WALL_TORCH, + _ => unreachable!(), + }; + let mut torch_props = WallTorchProps::default(&wall_block); + torch_props.facing = face.to_horizontal_facing().unwrap().opposite(); + return torch_props.to_state_id(&wall_block); + } + block.default_state_id + } + } + + manager.register(TorchBlock { id: block }); + } +} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index c99eaa06d..b8948d279 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -2,7 +2,19 @@ use blocks::doors::register_door_blocks; use blocks::fence_gates::register_fence_gate_blocks; use blocks::fences::register_fence_blocks; use blocks::logs::register_log_blocks; -use blocks::{chest::ChestBlock, furnace::FurnaceBlock, lever::LeverBlock, tnt::TNTBlock}; +use blocks::redstone::buttons::register_button_blocks; +use blocks::redstone::observer::ObserverBlock; +use blocks::redstone::piston::PistonBlock; +use blocks::redstone::redstone_block::RedstoneBlock; +use blocks::redstone::redstone_lamp::RedstoneLamp; +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::torches::register_torch_blocks; +use blocks::{ + chest::ChestBlock, furnace::FurnaceBlock, redstone::lever::LeverBlock, tnt::TNTBlock, +}; use pumpkin_data::block::{Block, BlockState}; use pumpkin_data::entity::EntityType; use pumpkin_data::item::Item; @@ -35,11 +47,21 @@ pub fn default_registry() -> Arc { manager.register(ChestBlock); manager.register(TNTBlock); manager.register(LeverBlock); + manager.register(RedstoneWireBlock); + manager.register(RedstoneBlock); + manager.register(RedstoneLamp); + manager.register(RepeaterBlock); + manager.register(ObserverBlock); + manager.register(PistonBlock); + manager.register(TargetBlock); register_door_blocks(&mut manager); register_fence_blocks(&mut manager); register_fence_gate_blocks(&mut manager); register_log_blocks(&mut manager); + register_button_blocks(&mut manager); + register_torch_blocks(&mut manager); + register_redstone_torch_blocks(&mut manager); Arc::new(manager) } diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index a2490cf22..cbc666f61 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -1,7 +1,7 @@ use crate::block::registry::BlockActionResult; use crate::entity::player::Player; use crate::server::Server; -use crate::world::World; +use crate::world::{BlockFlags, World}; use async_trait::async_trait; use pumpkin_data::block::{Block, BlockState, HorizontalFacing}; use pumpkin_data::item::Item; @@ -47,6 +47,7 @@ pub trait PumpkinBlock: Send + Sync { } #[allow(clippy::too_many_arguments)] + /// getPlacementState in source code async fn on_place( &self, _server: &Server, @@ -61,25 +62,19 @@ pub trait PumpkinBlock: Send + Sync { block.default_state_id } - async fn can_place( - &self, - _server: &Server, - _world: &World, - _block: &Block, - _face: &BlockDirection, - _block_pos: &BlockPos, - _player_direction: &HorizontalFacing, - ) -> bool { + async fn can_place_at(&self, _world: &World, _block_pos: &BlockPos) -> bool { true } + /// onBlockAdded in source code async fn placed( &self, - _block: &Block, - _player: &Player, - _location: BlockPos, - _server: &Server, _world: &World, + _block: &Block, + _state_id: u16, + _block_pos: &BlockPos, + _old_state_id: u16, + _notify: bool, ) { } @@ -106,12 +101,82 @@ pub trait PumpkinBlock: Send + Sync { async fn on_neighbor_update( &self, - _server: &Server, _world: &World, _block: &Block, _block_pos: &BlockPos, - _source_face: &BlockDirection, - _source_block_pos: &BlockPos, + _source_block: &Block, + _notify: bool, ) { } + + /// Called if a block state is replaced or it replaces another state + async fn prepare( + &self, + _world: &World, + _block_pos: &BlockPos, + _block: &Block, + _state_id: u16, + _flags: BlockFlags, + ) { + } + + #[allow(clippy::too_many_arguments)] + async fn get_state_for_neighbor_update( + &self, + _world: &World, + _block: &Block, + state: u16, + _block_pos: &BlockPos, + _direction: &BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: u16, + ) -> u16 { + state + } + + async fn on_scheduled_tick(&self, _world: &World, _block: &Block, _block_pos: &BlockPos) {} + + async fn on_state_replaced( + &self, + _world: &World, + _block: &Block, + _location: BlockPos, + _old_state_id: u16, + _moved: bool, + ) { + } + + /// Sides where redstone connects to + async fn emits_redstone_power( + &self, + _block: &Block, + _state: &BlockState, + _direction: &BlockDirection, + ) -> bool { + false + } + + /// Weak redstone power, aka. block that should be powered needs to be directly next to the source block + async fn get_weak_redstone_power( + &self, + _block: &Block, + _world: &World, + _block_pos: &BlockPos, + _state: &BlockState, + _direction: &BlockDirection, + ) -> u8 { + 0 + } + + /// Strong redstone power. this can power a block that then gives power + async fn get_strong_redstone_power( + &self, + _block: &Block, + _world: &World, + _block_pos: &BlockPos, + _state: &BlockState, + _direction: &BlockDirection, + ) -> u8 { + 0 + } } diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index b80621d65..a9bcb86be 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -1,7 +1,7 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::entity::player::Player; use crate::server::Server; -use crate::world::World; +use crate::world::{BlockFlags, World}; use pumpkin_data::block::{Block, BlockState, HorizontalFacing}; use pumpkin_data::item::Item; use pumpkin_inventory::OpenContainer; @@ -99,20 +99,10 @@ impl BlockRegistry { block.default_state_id } - pub async fn can_place( - &self, - server: &Server, - world: &World, - block: &Block, - face: &BlockDirection, - block_pos: &BlockPos, - player_direction: &HorizontalFacing, - ) -> bool { + 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 { - return pumpkin_block - .can_place(server, world, block, face, block_pos, player_direction) - .await; + return pumpkin_block.can_place_at(world, block_pos).await; } true } @@ -121,17 +111,17 @@ impl BlockRegistry { &self, world: &World, block: &Block, - player: &Player, - location: BlockPos, - server: &Server, + state_id: u16, + block_pos: &BlockPos, + old_state_id: u16, + notify: bool, ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { pumpkin_block - .placed(block, player, location, server, world) + .placed(world, block, state_id, block_pos, old_state_id, notify) .await; } - world.update_neighbors(server, &location, None).await; } pub async fn broken( @@ -149,7 +139,6 @@ impl BlockRegistry { .broken(block, player, location, server, world.clone(), state) .await; } - world.update_neighbors(server, &location, None).await; } pub async fn close( @@ -168,9 +157,183 @@ impl BlockRegistry { } } + pub async fn on_state_replaced( + &self, + world: &World, + block: &Block, + location: BlockPos, + old_state_id: u16, + moved: bool, + ) { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .on_state_replaced(world, block, location, old_state_id, moved) + .await; + } + } + + /// Updates state of all neighbors of the block + pub async fn post_process_state( + &self, + world: &World, + location: &BlockPos, + block: &Block, + flags: BlockFlags, + ) { + let state = world.get_block_state(location).await.unwrap(); + for direction in BlockDirection::all() { + let neighbor_pos = location.offset(direction.to_offset()); + let neighbor_state = world.get_block_state(&neighbor_pos).await.unwrap(); + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + let new_state = pumpkin_block + .get_state_for_neighbor_update( + world, + block, + state.id, + location, + &direction.opposite(), + &neighbor_pos, + neighbor_state.id, + ) + .await; + world.set_block_state(&neighbor_pos, new_state, flags).await; + } + } + } + + pub async fn prepare( + &self, + world: &World, + block_pos: &BlockPos, + block: &Block, + state_id: u16, + flags: BlockFlags, + ) { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .prepare(world, block_pos, block, state_id, flags) + .await; + } + } + + #[allow(clippy::too_many_arguments)] + pub async fn get_state_for_neighbor_update( + &self, + world: &World, + block: &Block, + state: u16, + block_pos: &BlockPos, + direction: &BlockDirection, + neighbor_pos: &BlockPos, + neighbor_state: u16, + ) -> u16 { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + return pumpkin_block + .get_state_for_neighbor_update( + world, + block, + state, + block_pos, + direction, + neighbor_pos, + neighbor_state, + ) + .await; + } + state + } + + pub async fn update_neighbors( + &self, + world: &World, + block_pos: &BlockPos, + _block: &Block, + flags: BlockFlags, + ) { + for direction in BlockDirection::abstract_block_update_order() { + let pos = block_pos.offset(direction.to_offset()); + + Box::pin(world.replace_with_state_for_neighbor_update( + &pos, + &direction.opposite(), + flags, + )) + .await; + } + } + + pub async fn on_neighbor_update( + &self, + world: &World, + block: &Block, + block_pos: &BlockPos, + source_block: &Block, + notify: bool, + ) { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .on_neighbor_update(world, block, block_pos, source_block, notify) + .await; + } + } + #[must_use] pub fn get_pumpkin_block(&self, block: &Block) -> Option<&Arc> { self.blocks .get(format!("minecraft:{}", block.name).as_str()) } + + pub async fn emits_redstone_power( + &self, + block: &Block, + state: &BlockState, + direction: &BlockDirection, + ) -> bool { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + return pumpkin_block + .emits_redstone_power(block, state, direction) + .await; + } + false + } + + pub async fn get_weak_redstone_power( + &self, + block: &Block, + world: &World, + block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + return pumpkin_block + .get_weak_redstone_power(block, world, block_pos, state, direction) + .await; + } + 0 + } + + pub async fn get_strong_redstone_power( + &self, + block: &Block, + world: &World, + block_pos: &BlockPos, + state: &BlockState, + direction: &BlockDirection, + ) -> u8 { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + return pumpkin_block + .get_strong_redstone_power(block, world, block_pos, state, direction) + .await; + } + 0 + } } diff --git a/pumpkin/src/command/commands/fill.rs b/pumpkin/src/command/commands/fill.rs index 6e1820e6b..49f956d36 100644 --- a/pumpkin/src/command/commands/fill.rs +++ b/pumpkin/src/command/commands/fill.rs @@ -4,6 +4,7 @@ use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::tree::CommandTree; use crate::command::tree::builder::{argument, literal}; use crate::command::{CommandError, CommandExecutor, CommandSender}; +use crate::world::BlockFlags; use async_trait::async_trait; use pumpkin_util::math::position::BlockPos; @@ -70,8 +71,20 @@ impl CommandExecutor for Executor { for y in start_y..=end_y { for z in start_z..=end_z { let block_position = BlockPos(Vector3 { x, y, z }); - world.break_block(&block_position, None, false, None).await; - world.set_block_state(&block_position, block_state_id).await; + world + .break_block( + &block_position, + None, + BlockFlags::SKIP_DROPS | BlockFlags::FORCE_STATE, + ) + .await; + world + .set_block_state( + &block_position, + block_state_id, + BlockFlags::FORCE_STATE, + ) + .await; placed_blocks += 1; } } @@ -82,7 +95,13 @@ impl CommandExecutor for Executor { for y in start_y..=end_y { for z in start_z..=end_z { let block_position = BlockPos(Vector3 { x, y, z }); - world.set_block_state(&block_position, block_state_id).await; + world + .set_block_state( + &block_position, + block_state_id, + BlockFlags::FORCE_STATE, + ) + .await; placed_blocks += 1; } } @@ -95,7 +114,13 @@ impl CommandExecutor for Executor { let block_position = BlockPos(Vector3 { x, y, z }); match world.get_block_state(&block_position).await { Ok(old_state) if old_state.air => { - world.set_block_state(&block_position, block_state_id).await; + world + .set_block_state( + &block_position, + block_state_id, + BlockFlags::FORCE_STATE, + ) + .await; placed_blocks += 1; } _ => {} @@ -116,9 +141,17 @@ impl CommandExecutor for Executor { || z == start_z || z == end_z; if is_edge { - world.set_block_state(&block_position, block_state_id).await; + world + .set_block_state( + &block_position, + block_state_id, + BlockFlags::FORCE_STATE, + ) + .await; } else { - world.set_block_state(&block_position, 0).await; + world + .set_block_state(&block_position, 0, BlockFlags::FORCE_STATE) + .await; } placed_blocks += 1; } @@ -137,7 +170,13 @@ impl CommandExecutor for Executor { || z == start_z || z == end_z; if is_edge { - world.set_block_state(&block_position, block_state_id).await; + world + .set_block_state( + &block_position, + block_state_id, + BlockFlags::FORCE_STATE, + ) + .await; placed_blocks += 1; } } diff --git a/pumpkin/src/command/commands/setblock.rs b/pumpkin/src/command/commands/setblock.rs index 4d86b7899..e1f182c5a 100644 --- a/pumpkin/src/command/commands/setblock.rs +++ b/pumpkin/src/command/commands/setblock.rs @@ -7,6 +7,7 @@ use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::tree::CommandTree; use crate::command::tree::builder::{argument, literal}; use crate::command::{CommandError, CommandExecutor, CommandSender}; +use crate::world::BlockFlags; const NAMES: [&str; 1] = ["setblock"]; @@ -49,17 +50,26 @@ impl CommandExecutor for Executor { let success = match mode { Mode::Destroy => { - world.clone().break_block(&pos, None, false, None).await; - world.set_block_state(&pos, block_state_id).await; + world + .clone() + .break_block(&pos, None, BlockFlags::SKIP_DROPS | BlockFlags::FORCE_STATE) + .await; + world + .set_block_state(&pos, block_state_id, BlockFlags::FORCE_STATE) + .await; true } Mode::Replace => { - world.set_block_state(&pos, block_state_id).await; + world + .set_block_state(&pos, block_state_id, BlockFlags::FORCE_STATE) + .await; true } Mode::Keep => match world.get_block_state(&pos).await { Ok(old_state) if old_state.air => { - world.set_block_state(&pos, block_state_id).await; + world + .set_block_state(&pos, block_state_id, BlockFlags::FORCE_STATE) + .await; true } Ok(_) => false, diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index c03b3fc4d..383981fe1 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -8,6 +8,7 @@ use crate::net::PlayerConfig; use crate::plugin::player::player_chat::PlayerChatEvent; use crate::plugin::player::player_command_send::PlayerCommandSendEvent; use crate::plugin::player::player_move::PlayerMoveEvent; +use crate::world::BlockFlags; use crate::{ command::CommandSender, entity::player::{ChatMode, Hand, Player}, @@ -962,7 +963,11 @@ impl Player { let broken_state = world.get_block_state(&location).await.unwrap(); world - .break_block(&location, Some(self.clone()), false, None) + .break_block( + &location, + Some(self.clone()), + BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS, + ) .await; server .block_registry @@ -987,7 +992,11 @@ impl Player { if speed >= 1.0 { let broken_state = world.get_block_state(&location).await.unwrap(); world - .break_block(&location, Some(self.clone()), true, None) + .break_block( + &location, + Some(self.clone()), + BlockFlags::NOTIFY_NEIGHBORS, + ) .await; server .block_registry @@ -1055,7 +1064,15 @@ impl Player { let drop = self.gamemode.load() != GameMode::Creative && self.can_harvest(&state, block.name).await; world - .break_block(&location, Some(self.clone()), drop, None) + .break_block( + &location, + Some(self.clone()), + if drop { + BlockFlags::NOTIFY_NEIGHBORS + } else { + BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS + }, + ) .await; } server @@ -1515,20 +1532,11 @@ impl Player { if !intersects && server .block_registry - .can_place( - server, - world, - &block, - face, - &final_block_pos, - &self.get_player_direction(), - ) + .can_place_at(world, &block, &final_block_pos) .await { - let _replaced_id = world.set_block_state(&final_block_pos, new_state).await; - server - .block_registry - .on_placed(world, &block, self, final_block_pos, server) + let _replaced_id = world + .set_block_state(&final_block_pos, new_state, BlockFlags::NOTIFY_ALL) .await; self.send_sign_packet(block, final_block_pos, face).await; diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 17b44cb44..1aeeaf8e7 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -98,12 +98,15 @@ impl Server { // First register the default commands. After that, plugins can put in their own. let command_dispatcher = RwLock::new(default_dispatcher()); + let block_registry = super::block::default_registry(); + let world = World::load( Dimension::Overworld.into_level( // TODO: load form config "./world".parse().unwrap(), ), DimensionType::Overworld, + block_registry.clone(), ); Self { @@ -119,7 +122,7 @@ impl Server { DimensionType::TheEnd, ], command_dispatcher, - block_registry: super::block::default_registry(), + block_registry, item_registry: super::item::items::default_registry(), auth_client, key_store: KeyStore::new(), diff --git a/pumpkin/src/world/chunker.rs b/pumpkin/src/world/chunker.rs index 1f6a172a9..a8627429c 100644 --- a/pumpkin/src/world/chunker.rs +++ b/pumpkin/src/world/chunker.rs @@ -2,7 +2,6 @@ use std::{num::NonZeroU8, sync::Arc}; use pumpkin_config::BASIC_CONFIG; use pumpkin_protocol::client::play::{CCenterChunk, CUnloadChunk}; -use pumpkin_util::math::{get_section_cord, position::BlockPos, vector3::Vector3}; use pumpkin_world::cylindrical_chunk_iterator::Cylindrical; use crate::entity::player::Player; @@ -101,13 +100,3 @@ pub async fn update_position(player: &Arc) { } } } - -#[must_use] -pub const fn chunk_section_from_pos(block_pos: &BlockPos) -> Vector3 { - let block_pos = block_pos.0; - Vector3::new( - get_section_cord(block_pos.x), - get_section_cord(block_pos.y), - get_section_cord(block_pos.z), - ) -} diff --git a/pumpkin/src/world/explosion.rs b/pumpkin/src/world/explosion.rs index c8cb7bea0..5242d5fd3 100644 --- a/pumpkin/src/world/explosion.rs +++ b/pumpkin/src/world/explosion.rs @@ -5,7 +5,7 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use crate::{block::drop_loot, server::Server}; -use super::World; +use super::{BlockFlags, World}; pub struct Explosion { power: f32, @@ -81,7 +81,7 @@ impl Explosion { let block = world.get_block(&pos).await.unwrap(); let pumpkin_block = server.block_registry.get_pumpkin_block(&block); - world.set_block_state(&pos, 0).await; + world.set_block_state(&pos, 0, BlockFlags::NOTIFY_ALL).await; if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) { drop_loot(world, &block, &pos, false, block_state.id).await; diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index ef667c43e..e54ad3c66 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -8,7 +8,8 @@ pub mod explosion; pub mod time; use crate::{ - PLUGIN_MANAGER, block, + PLUGIN_MANAGER, + block::{self, registry::BlockRegistry}, command::client_suggestions, entity::{Entity, EntityBase, EntityId, player::Player}, error::PumpkinError, @@ -19,11 +20,13 @@ use crate::{ }, server::Server, }; +use bitflags::bitflags; use border::Worldborder; use bytes::Bytes; use explosion::Explosion; use pumpkin_config::BasicConfiguration; use pumpkin_data::{ + block::Block, entity::{EntityStatus, EntityType}, particle::Particle, sound::{Sound, SoundCategory}, @@ -33,8 +36,8 @@ use pumpkin_macros::send_cancellable; use pumpkin_protocol::{ ClientPacket, IdOr, SoundEvent, client::play::{ - CEntityStatus, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, - CSoundEffect, CSpawnEntity, GameEvent, PlayerAction, + CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate, CPlayerInfoUpdate, CRemoveEntities, + CRemovePlayerInfo, CSoundEffect, CSpawnEntity, GameEvent, PlayerAction, }, }; use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier}; @@ -46,10 +49,9 @@ use pumpkin_protocol::{ codec::var_int::VarInt, }; use pumpkin_registry::DimensionType; -use pumpkin_util::math::vector2::Vector2; 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::level::Level; use pumpkin_world::level::SyncChunk; use pumpkin_world::{block::BlockDirection, chunk::ChunkData}; use pumpkin_world::{ @@ -58,6 +60,7 @@ use pumpkin_world::{ }, coordinates::ChunkRelativeBlockCoordinates, }; +use pumpkin_world::{chunk::TickPriority, level::Level}; use rand::{Rng, thread_rng}; use scoreboard::Scoreboard; use thiserror::Error; @@ -76,6 +79,21 @@ pub mod weather; use weather::Weather; +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct BlockFlags: u32 { + const NOTIFY_NEIGHBORS = 0b000_0000_0001; + const NOTIFY_LISTENERS = 0b000_0000_0010; + const NOTIFY_ALL = 0b000_0000_0011; + const FORCE_STATE = 0b000_0000_0100; + const SKIP_DROPS = 0b000_0000_1000; + const MOVED = 0b000_0001_0000; + const SKIP_REDSTONE_WIRE_STATE_REPLACEMENT = 0b000_0010_0000; + const SKIP_BLOCK_ENTITY_REPLACED_CALLBACK = 0b000_0100_0000; + const SKIP_BLOCK_ADDED_CALLBACK = 0b000_1000_0000; + } +} + #[derive(Debug, Error)] pub enum GetBlockError { BlockOutOfWorldBounds, @@ -129,12 +147,20 @@ pub struct World { pub dimension_type: DimensionType, /// The world's weather, including rain and thunder levels. pub weather: Mutex, + /// Block Behaviour + pub block_registry: Arc, + /// A map of unsent block changes, keyed by block position. + unsent_block_changes: Mutex>, // TODO: entities } impl World { #[must_use] - pub fn load(level: Level, dimension_type: DimensionType) -> Self { + pub fn load( + level: Level, + dimension_type: DimensionType, + block_registry: Arc, + ) -> Self { Self { level: Arc::new(level), players: Arc::new(RwLock::new(HashMap::new())), @@ -144,6 +170,8 @@ impl World { level_time: Mutex::new(LevelTime::new()), dimension_type, weather: Mutex::new(Weather::new()), + block_registry, + unsent_block_changes: Mutex::new(HashMap::new()), } } @@ -283,7 +311,9 @@ impl World { } pub async fn tick(&self, server: &Server) { - // World ticks + self.flush_block_updates().await; + + // world ticks { let mut level_time = self.level_time.lock().await; level_time.tick_time(); @@ -297,7 +327,9 @@ impl World { weather.tick_weather(self).await; }; - // Player ticks + self.tick_scheduled_block_ticks().await; + + // player ticks for player in self.players.read().await.values() { player.tick(server).await; } @@ -329,6 +361,52 @@ impl World { } } + pub async fn flush_block_updates(&self) { + let mut block_state_updates_by_chunk_section = HashMap::new(); + for (position, block_state_id) in self.unsent_block_changes.lock().await.drain() { + let chunk_section = chunk_section_from_pos(&position); + block_state_updates_by_chunk_section + .entry(chunk_section) + .or_insert(Vec::new()) + .push((position, block_state_id)); + } + + // TODO: only send packet to players who have the chunks loaded + // TODO: Send light updates to update the wire directly next to a broken block + for chunk_section in block_state_updates_by_chunk_section.values() { + if chunk_section.is_empty() { + continue; + } + if chunk_section.len() == 1 { + let (block_pos, block_state_id) = chunk_section[0]; + self.broadcast_packet_all(&CBlockUpdate::new( + block_pos, + i32::from(block_state_id).into(), + )) + .await; + } else { + self.broadcast_packet_all(&CMultiBlockUpdate::new(chunk_section.clone())) + .await; + } + } + } + + pub async fn tick_scheduled_block_ticks(&self) { + let blocks_to_tick = self.level.get_and_tick_block_ticks().await; + + for scheduled_tick in blocks_to_tick { + let block = self.get_block(&scheduled_tick.block_pos).await.unwrap(); + if scheduled_tick.target_block_id != block.id { + continue; + } + if let Some(pumpkin_block) = self.block_registry.get_pumpkin_block(&block) { + pumpkin_block + .on_scheduled_tick(self, &block, &scheduled_tick.block_pos) + .await; + } + } + } + /// Gets the y position of the first non air block from the top down pub async fn get_top_block(&self, position: Vector2) -> i32 { for y in (-64..=319).rev() { @@ -1073,29 +1151,138 @@ impl World { .await; } - /// Sets a block. - pub async fn set_block_state(&self, position: &BlockPos, block_state_id: u16) -> u16 { + /// Sets a block + pub async fn set_block_state( + &self, + position: &BlockPos, + block_state_id: u16, + flags: BlockFlags, + ) -> u16 { let (chunk_coordinate, relative_coordinates) = position.chunk_and_chunk_relative_position(); // Since we divide by 16, remnant can never exceed `u8::MAX` let relative = ChunkRelativeBlockCoordinates::from(relative_coordinates); - let chunk = self.receive_chunk(chunk_coordinate).await.0; + let chunk = match self.level.try_get_chunk(chunk_coordinate) { + Some(chunk) => chunk.clone(), + None => self.receive_chunk(chunk_coordinate).await.0, + }; let mut chunk = chunk.write().await; + let replaced_block_state_id = chunk + .blocks + .get_block(relative) + .unwrap_or(Block::AIR.default_state_id); + if replaced_block_state_id == block_state_id { + return block_state_id; + } chunk.dirty = true; - let replaced_block_state_id = chunk.blocks.get_block(relative).unwrap(); + chunk.blocks.set_block(relative, block_state_id); + self.unsent_block_changes + .lock() + .await + .insert(*position, block_state_id); drop(chunk); - self.broadcast_packet_all(&CBlockUpdate::new( - *position, - i32::from(block_state_id).into(), - )) - .await; + let old_block = Block::from_state_id(replaced_block_state_id).unwrap(); + let new_block = Block::from_state_id(block_state_id).unwrap(); + + let block_moved = flags.contains(BlockFlags::MOVED); + + // WorldChunk.java line 310 + if old_block != new_block && (flags.contains(BlockFlags::NOTIFY_NEIGHBORS) || block_moved) { + self.block_registry + .on_state_replaced( + self, + &old_block, + *position, + replaced_block_state_id, + block_moved, + ) + .await; + } + + let block_state = self.get_block_state(position).await.unwrap(); + let new_block = Block::from_state_id(block_state_id).unwrap(); + + // WorldChunk.java line 318 + if !flags.contains(BlockFlags::SKIP_BLOCK_ADDED_CALLBACK) && new_block != old_block { + self.block_registry + .on_placed( + self, + &new_block, + block_state_id, + position, + replaced_block_state_id, + block_moved, + ) + .await; + } + + // Ig they do this cause it could be modified in chunkPos.setBlockState? + if block_state.id == block_state_id { + if flags.contains(BlockFlags::NOTIFY_LISTENERS) { + // Mob AI update + } + + if flags.contains(BlockFlags::NOTIFY_NEIGHBORS) { + self.update_neighbors(position, None).await; + // TODO: updateComparators + } + + if !flags.contains(BlockFlags::FORCE_STATE) { + let mut new_flags = flags; + new_flags.remove(BlockFlags::NOTIFY_NEIGHBORS); + new_flags.remove(BlockFlags::NOTIFY_LISTENERS); + self.block_registry + .prepare( + self, + position, + &Block::from_state_id(replaced_block_state_id).unwrap(), + replaced_block_state_id, + new_flags, + ) + .await; + self.block_registry + .update_neighbors( + self, + position, + &Block::from_state_id(block_state_id).unwrap(), + new_flags, + ) + .await; + self.block_registry + .prepare( + self, + position, + &Block::from_state_id(block_state_id).unwrap(), + block_state_id, + new_flags, + ) + .await; + } + } replaced_block_state_id } + pub async fn schedule_block_tick( + &self, + block: &Block, + block_pos: BlockPos, + delay: u16, + priority: TickPriority, + ) { + self.level + .schedule_block_tick(block.id, &block_pos, delay, priority) + .await; + } + + pub async fn is_block_tick_scheduled(&self, block_pos: &BlockPos, block: &Block) -> bool { + self.level + .is_block_tick_scheduled(block_pos, block.id) + .await + } // Stream the chunks (don't collect them and then do stuff with them) /// Spawns a tokio task to stream chunks. /// Important: must be called from an async function (or changed to accept a tokio runtime @@ -1130,13 +1317,11 @@ impl World { .expect("Channel closed for unknown reason") } - /// If `server` is sent, it will do a block update. pub async fn break_block( self: &Arc, position: &BlockPos, cause: Option>, - drop: bool, - server: Option<&Server>, + flags: BlockFlags, ) { let block = self.get_block(position).await.unwrap(); let event = BlockBreakEvent::new(cause.clone(), block.clone(), 0, false); @@ -1148,7 +1333,7 @@ impl World { .await; if !event.cancelled { - let broken_block_state_id = self.set_block_state(position, 0).await; + let broken_block_state_id = self.set_block_state(position, 0, flags).await; let particles_packet = CWorldEvent::new( WorldEvent::BlockBroken as i32, @@ -1157,7 +1342,7 @@ impl World { false, ); - if drop { + if !flags.contains(BlockFlags::SKIP_DROPS) { block::drop_loot(self, &block, position, true, broken_block_state_id).await; } @@ -1168,17 +1353,16 @@ impl World { } None => self.broadcast_packet_all(&particles_packet).await, } - - if let Some(server) = server { - self.update_neighbors(server, position, None).await; - } } } pub async fn get_block_state_id(&self, position: &BlockPos) -> Result { - let (chunk, relative) = position.chunk_and_chunk_relative_position(); + let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let relative = ChunkRelativeBlockCoordinates::from(relative); - let chunk = self.receive_chunk(chunk).await.0; + let chunk = match self.level.try_get_chunk(chunk_coordinate) { + Some(chunk) => chunk.clone(), + None => self.receive_chunk(chunk_coordinate).await.0, + }; let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; let Some(id) = chunk.blocks.get_block(relative) else { @@ -1206,7 +1390,14 @@ impl World { get_state_by_state_id(id).ok_or(GetBlockError::InvalidBlockId) } - /// Gets a `Block` + `BlockState` from the block registry. Returns `None` if the block state has not been found. + pub fn get_state_by_id( + &self, + id: u16, + ) -> Result { + get_state_by_state_id(id).ok_or(GetBlockError::InvalidBlockId) + } + + /// Gets the Block + Block state from the Block Registry, Returns None if the Block state has not been found pub async fn get_block_and_block_state( &self, position: &BlockPos, @@ -1215,13 +1406,9 @@ impl World { get_block_and_state_by_state_id(id).ok_or(GetBlockError::InvalidBlockId) } - /// Updates neighboring blocks of a block. - pub async fn update_neighbors( - &self, - server: &Server, - block_pos: &BlockPos, - except: Option<&BlockDirection>, - ) { + /// Updates neighboring blocks of a block + pub async fn update_neighbors(&self, block_pos: &BlockPos, except: Option<&BlockDirection>) { + let source_block = self.get_block(block_pos).await.unwrap(); for direction in BlockDirection::update_order() { if Some(&direction) == except { continue; @@ -1230,20 +1417,71 @@ impl World { let neighbor_block = self.get_block(&neighbor_pos).await; if let Ok(neighbor_block) = neighbor_block { if let Some(neighbor_pumpkin_block) = - server.block_registry.get_pumpkin_block(&neighbor_block) + self.block_registry.get_pumpkin_block(&neighbor_block) { neighbor_pumpkin_block .on_neighbor_update( - server, self, &neighbor_block, &neighbor_pos, - &direction, - block_pos, + &source_block, + false, ) .await; } } } } + + pub async fn update_neighbor(&self, neighbor_block_pos: &BlockPos, source_block: &Block) { + let neighbor_block = self.get_block(neighbor_block_pos).await.unwrap(); + + if let Some(neighbor_pumpkin_block) = self.block_registry.get_pumpkin_block(&neighbor_block) + { + neighbor_pumpkin_block + .on_neighbor_update( + self, + &neighbor_block, + neighbor_block_pos, + source_block, + false, + ) + .await; + } + } + + pub async fn replace_with_state_for_neighbor_update( + &self, + block_pos: &BlockPos, + direction: &BlockDirection, + flags: BlockFlags, + ) { + let (block, block_state) = self.get_block_and_block_state(block_pos).await.unwrap(); + + if flags.contains(BlockFlags::SKIP_REDSTONE_WIRE_STATE_REPLACEMENT) + && block.id == Block::REDSTONE_WIRE.id + { + return; + } + + let neighbor_pos = block_pos.offset(direction.to_offset()); + let neighbor_state_id = self.get_block_state_id(&neighbor_pos).await.unwrap(); + + let new_state_id = self + .block_registry + .get_state_for_neighbor_update( + self, + &block, + block_state.id, + block_pos, + direction, + &neighbor_pos, + neighbor_state_id, + ) + .await; + + if new_state_id != block_state.id { + self.set_block_state(block_pos, new_state_id, flags).await; + } + } }