diff --git a/crates/pumpkin/src/block/blocks/mod.rs b/crates/pumpkin/src/block/blocks/mod.rs index 068e6c351..a14241817 100644 --- a/crates/pumpkin/src/block/blocks/mod.rs +++ b/crates/pumpkin/src/block/blocks/mod.rs @@ -77,6 +77,7 @@ pub mod logs; pub mod mangrove_roots; pub mod plant; pub mod pumpkin; +pub mod sculk; pub mod vine; // Terrain / environment / physics @@ -98,6 +99,7 @@ pub mod cake; pub mod cauldron; pub mod composter; pub mod ladder; +pub mod respawn_anchor; pub mod slime; pub mod spawner; pub mod tnt; diff --git a/crates/pumpkin/src/block/blocks/respawn_anchor.rs b/crates/pumpkin/src/block/blocks/respawn_anchor.rs new file mode 100644 index 000000000..0103b06f9 --- /dev/null +++ b/crates/pumpkin/src/block/blocks/respawn_anchor.rs @@ -0,0 +1,119 @@ +use pumpkin_data::block_properties::{BlockProperties, RespawnAnchorLikeProperties}; +use pumpkin_data::dimension::Dimension; +use pumpkin_data::item::Item; +use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::translation; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::text::TextComponent; +use pumpkin_world::world::BlockFlags; + +use crate::block::registry::BlockActionResult; +use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, UseWithItemArgs}; +use crate::entity::EntityBase; + +#[pumpkin_block("minecraft:respawn_anchor")] +pub struct RespawnAnchorBlock; + +impl BlockBehaviour for RespawnAnchorBlock { + fn use_with_item<'a>( + &'a self, + args: UseWithItemArgs<'a>, + ) -> BlockFuture<'a, BlockActionResult> { + Box::pin(async move { + if args.item_stack.item.id != Item::GLOWSTONE.id { + return BlockActionResult::Pass; + } + + let state_id = args.world.get_block_state_id(args.position); + let mut props = RespawnAnchorLikeProperties::from_state_id(state_id, args.block); + + if props.charges >= 4 { + return BlockActionResult::Pass; + } + + props.charges += 1; + args.world + .set_block_state( + args.position, + props.to_state_id(args.block), + BlockFlags::NOTIFY_ALL, + ) + .await; + + args.item_stack + .decrement_unless_creative(args.player.gamemode.load(), 1); + + args.world.play_sound( + Sound::BlockRespawnAnchorCharge, + SoundCategory::Blocks, + &args.position.to_f64(), + ); + + BlockActionResult::Success + }) + } + + fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { + Box::pin(async move { + let state_id = args.world.get_block_state_id(args.position); + let mut props = RespawnAnchorLikeProperties::from_state_id(state_id, args.block); + + if args.world.dimension != Dimension::THE_NETHER { + args.world + .break_block(args.position, None, BlockFlags::SKIP_DROPS) + .await; + args.world + .explode(args.position.to_centered_f64(), 5.0) + .await; + return BlockActionResult::SuccessServer; + } + + if props.charges == 0 { + args.player + .send_system_message(&TextComponent::translate( + translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP, + &[], + )) + .await; + return BlockActionResult::SuccessServer; + } + + if args + .player + .set_respawn_point( + args.world.dimension.clone(), + *args.position, + args.player.get_entity().yaw.load(), + args.player.get_entity().pitch.load(), + false, + ) + .await + { + props.charges -= 1; + args.world + .set_block_state( + args.position, + props.to_state_id(args.block), + BlockFlags::NOTIFY_ALL, + ) + .await; + + args.world.play_sound( + Sound::BlockRespawnAnchorSetSpawn, + SoundCategory::Blocks, + &args.position.to_f64(), + ); + + args.player + .send_system_message(&TextComponent::translate_cross( + translation::java::BLOCK_MINECRAFT_SET_SPAWN, + translation::bedrock::TILE_BED_RESPAWNSET, + [], + )) + .await; + } + + BlockActionResult::SuccessServer + }) + } +} diff --git a/crates/pumpkin/src/block/blocks/sculk/mod.rs b/crates/pumpkin/src/block/blocks/sculk/mod.rs new file mode 100644 index 000000000..c94ab72d8 --- /dev/null +++ b/crates/pumpkin/src/block/blocks/sculk/mod.rs @@ -0,0 +1,3 @@ +pub mod sculk_catalyst; +pub mod sculk_shrieker; +pub mod sculk_vein; diff --git a/crates/pumpkin/src/block/blocks/sculk/sculk_catalyst.rs b/crates/pumpkin/src/block/blocks/sculk/sculk_catalyst.rs new file mode 100644 index 000000000..97fee6577 --- /dev/null +++ b/crates/pumpkin/src/block/blocks/sculk/sculk_catalyst.rs @@ -0,0 +1,23 @@ +use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs}; +use pumpkin_data::{ + BlockId, BlockStateId, + block_properties::{BlockProperties, SculkCatalystLikeProperties}, +}; + +pub struct SculkCatalystBlock; + +impl BlockMetadata for SculkCatalystBlock { + fn ids() -> Box<[BlockId]> { + [BlockId::SCULK_CATALYST].into() + } +} + +impl BlockBehaviour for SculkCatalystBlock { + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { + Box::pin(async move { + let mut props = SculkCatalystLikeProperties::default(args.block); + props.bloom = false; + props.to_state_id(args.block) + }) + } +} diff --git a/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs b/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs new file mode 100644 index 000000000..ee520a21d --- /dev/null +++ b/crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs @@ -0,0 +1,110 @@ +use std::sync::Arc; + +use crate::block::entities::sculk_shrieker::SculkShriekerBlockEntity; +use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs, OnScheduledTickArgs}; +use crate::world::World; +use pumpkin_data::potion::Effect; +use pumpkin_data::{ + BlockId, BlockStateId, + block_properties::{BlockProperties, SculkShriekerLikeProperties}, + effect::StatusEffect, + sound::{Sound, SoundCategory}, +}; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::tick::TickPriority; +use pumpkin_world::world::BlockFlags; + +const SHRIEK_TICKS: u8 = 90; +const DARKNESS_RADIUS: f64 = 40.0; +const DARKNESS_DURATION: i32 = 260; + +pub struct SculkShriekerBlock; + +impl BlockMetadata for SculkShriekerBlock { + fn ids() -> Box<[BlockId]> { + [BlockId::SCULK_SHRIEKER].into() + } +} + +impl SculkShriekerBlock { + pub async fn try_activate(world: &Arc, pos: &BlockPos) -> bool { + let block = world.get_block(pos); + if block.id != BlockId::SCULK_SHRIEKER { + return false; + } + let state = world.get_block_state(pos); + let mut props = SculkShriekerLikeProperties::from_state_id(state.id, block); + + if props.shrieking { + return false; + } + + props.shrieking = true; + world + .set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL) + .await; + + world.play_sound( + Sound::BlockSculkShriekerShriek, + SoundCategory::Blocks, + &pos.to_f64(), + ); + + world.schedule_block_tick(block, *pos, SHRIEK_TICKS, TickPriority::Normal); + + let center = pos.to_centered_f64(); + for player in world.get_nearby_players(center, DARKNESS_RADIUS) { + let darkness = Effect { + effect_type: &StatusEffect::DARKNESS, + duration: DARKNESS_DURATION, + amplifier: 0, + ambient: false, + show_particles: false, + show_icon: true, + blend: true, + }; + player.send_effect(darkness.clone()).await; + player.living_entity.add_effect(darkness).await; + } + + if let Some(entity) = world.get_block_entity(pos) + && let Some(shrieker) = entity.as_any().downcast_ref::() + { + let mut level = shrieker.warning_level.lock().await; + *level = (*level + 1).min(4); + if props.can_summon && *level >= 4 { + // TODO: spawn Warden near pos + } + } + + true + } +} + +impl BlockBehaviour for SculkShriekerBlock { + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { + Box::pin(async move { + let mut props = SculkShriekerLikeProperties::default(args.block); + props.shrieking = false; + props.waterlogged = args.replacing.water_source(); + props.to_state_id(args.block) + }) + } + + fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let state = args.world.get_block_state(args.position); + let mut props = SculkShriekerLikeProperties::from_state_id(state.id, args.block); + if props.shrieking { + props.shrieking = false; + args.world + .set_block_state( + args.position, + props.to_state_id(args.block), + BlockFlags::NOTIFY_ALL, + ) + .await; + } + }) + } +} diff --git a/crates/pumpkin/src/block/blocks/sculk/sculk_vein.rs b/crates/pumpkin/src/block/blocks/sculk/sculk_vein.rs new file mode 100644 index 000000000..3bad94711 --- /dev/null +++ b/crates/pumpkin/src/block/blocks/sculk/sculk_vein.rs @@ -0,0 +1,227 @@ +use std::collections::HashSet; + +use crate::block::{ + BlockBehaviour, BlockFuture, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs, CanUpdateAtArgs, + GetStateForNeighborUpdateArgs, OnPlaceArgs, UseWithItemArgs, registry::BlockActionResult, +}; +use crate::entity::{EntityBase, player::Player}; +use pumpkin_data::{ + Block, BlockDirection, BlockId, BlockStateId, FacingExt, + block_properties::{BlockProperties, GlowLichenLikeProperties}, + item::Item, +}; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::{BlockAccessor, BlockFlags}; + +pub struct SculkVeinBlock; + +impl BlockMetadata for SculkVeinBlock { + fn ids() -> Box<[BlockId]> { + [BlockId::SCULK_VEIN].into() + } +} + +impl BlockBehaviour for SculkVeinBlock { + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { + Box::pin(async move { + if let BlockIsReplacing::Itself(state_id) = args.replacing { + let (Some(direction), _) = get_attach_direction( + args.world, + args.position, + Some(args.player), + args.direction, + true, + ) else { + return Block::AIR.default_state.id; + }; + let mut props = GlowLichenLikeProperties::from_state_id(state_id, args.block); + set_face(&mut props, direction); + props.waterlogged = args.replacing.water_source(); + return props.to_state_id(args.block); + } + let (Some(direction), _) = get_attach_direction( + args.world, + args.position, + Some(args.player), + args.direction, + false, + ) else { + return Block::AIR.default_state.id; + }; + let mut props = GlowLichenLikeProperties::default(args.block); + set_face(&mut props, direction); + props.waterlogged = args.replacing.water_source(); + props.to_state_id(args.block) + }) + } + + fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { + get_attach_direction( + args.block_accessor, + args.position, + args.player, + args.direction.unwrap_or(BlockDirection::Down), + false, + ) + .0 + .is_some() + } + + fn can_update_at(&self, args: CanUpdateAtArgs<'_>) -> bool { + get_attach_direction( + args.world, + args.position, + Some(args.player), + args.direction, + true, + ) + .0 + .is_some() + } + + fn get_state_for_neighbor_update<'a>( + &'a self, + args: GetStateForNeighborUpdateArgs<'a>, + ) -> BlockFuture<'a, BlockStateId> { + Box::pin(async move { + let old_props = GlowLichenLikeProperties::from_state_id(args.state_id, args.block); + let mut new_directions = active_directions(old_props); + + let support = args + .world + .get_block(&args.position.offset(args.direction.to_offset())); + if !is_solid_face(support) { + new_directions.remove(&args.direction); + } + + if new_directions.is_empty() { + return Block::AIR.default_state.id; + } + let mut new_props = GlowLichenLikeProperties::default(args.block); + for dir in new_directions { + set_face(&mut new_props, dir); + } + new_props.to_state_id(args.block) + }) + } + + fn use_with_item<'a>( + &'a self, + args: UseWithItemArgs<'a>, + ) -> BlockFuture<'a, BlockActionResult> { + Box::pin(async move { + if args.item_stack.item.id != Item::SCULK_VEIN.id { + return BlockActionResult::Pass; + } + let state = args.world.get_block_state(args.position); + let mut props = GlowLichenLikeProperties::from_state_id(state.id, args.block); + + let (Some(accurate_dir), _) = get_attach_direction( + args.world.as_ref(), + args.position, + Some(args.player), + *args.hit.face, + true, + ) else { + return BlockActionResult::Fail; + }; + set_face(&mut props, accurate_dir); + + args.world + .set_block_state( + args.position, + props.to_state_id(args.block), + BlockFlags::NOTIFY_ALL, + ) + .await; + BlockActionResult::Consume + }) + } +} + +fn get_attach_direction( + block_accessor: &dyn BlockAccessor, + block_pos: &BlockPos, + player_wrapper: Option<&Player>, + click_direction: BlockDirection, + replacing: bool, +) -> (Option, bool) { + let clicked_block = block_accessor.get_block(&block_pos.offset(click_direction.to_offset())); + + if !replacing && clicked_block == &Block::SCULK_VEIN { + return (None, false); + } + + if is_solid_face(clicked_block) { + return (Some(click_direction), false); + } + + let (replacing_block, replacing_block_state) = block_accessor.get_block_and_state(block_pos); + let already_active = if replacing_block == &Block::SCULK_VEIN { + active_directions(GlowLichenLikeProperties::from_state_id( + replacing_block_state.id, + replacing_block, + )) + } else { + HashSet::new() + }; + + if let Some(player) = player_wrapper { + let fs = player.get_entity().get_entity_facing_order(); + let directions = [ + fs[0].to_block_direction(), + fs[1].to_block_direction(), + fs[2].to_block_direction(), + fs[3].to_block_direction(), + fs[4].to_block_direction(), + fs[5].to_block_direction(), + ]; + for dir in directions { + if !already_active.contains(&dir) { + let support = block_accessor.get_block(&block_pos.offset(dir.to_offset())); + if is_solid_face(support) { + return (Some(dir), false); + } + } + } + } + (None, false) +} + +const fn is_solid_face(block: &Block) -> bool { + block.default_state.is_full_cube() +} + +fn active_directions(props: GlowLichenLikeProperties) -> HashSet { + let mut set = HashSet::new(); + if props.down { + set.insert(BlockDirection::Down); + } + if props.up { + set.insert(BlockDirection::Up); + } + if props.north { + set.insert(BlockDirection::North); + } + if props.south { + set.insert(BlockDirection::South); + } + if props.east { + set.insert(BlockDirection::East); + } + if props.west { + set.insert(BlockDirection::West); + } + set +} + +const fn set_face(props: &mut GlowLichenLikeProperties, direction: BlockDirection) { + match direction { + BlockDirection::Down => props.down = true, + BlockDirection::Up => props.up = true, + BlockDirection::North => props.north = true, + BlockDirection::South => props.south = true, + BlockDirection::West => props.west = true, + BlockDirection::East => props.east = true, + } +} diff --git a/crates/pumpkin/src/block/registry.rs b/crates/pumpkin/src/block/registry.rs index 6be37614c..5aeb7a8ff 100644 --- a/crates/pumpkin/src/block/registry.rs +++ b/crates/pumpkin/src/block/registry.rs @@ -119,6 +119,9 @@ use crate::block::blocks::redstone::sculk_sensor::SculkSensorBlock; use crate::block::blocks::redstone::target_block::TargetBlock; use crate::block::blocks::redstone::tripwire::TripwireBlock; use crate::block::blocks::redstone::tripwire_hook::TripwireHookBlock; +use crate::block::blocks::sculk::sculk_catalyst::SculkCatalystBlock; +use crate::block::blocks::sculk::sculk_shrieker::SculkShriekerBlock; +use crate::block::blocks::sculk::sculk_vein::SculkVeinBlock; use crate::block::blocks::shelf::ShelfBlock; use crate::block::blocks::signs::SignBlock; use crate::block::blocks::slabs::SlabBlock; @@ -182,6 +185,7 @@ use crate::block::blocks::jukebox::JukeboxBlock; use crate::block::blocks::ladder::LadderBlock; use crate::block::blocks::lanterns::LanternBlock; use crate::block::blocks::lectern::LecternBlock; +use crate::block::blocks::respawn_anchor::RespawnAnchorBlock; use crate::block::blocks::shulker_box::ShulkerBoxBlock; use crate::block::blocks::skull_block::SkullBlock; use crate::block::blocks::smoker::SmokerBlock; @@ -280,6 +284,7 @@ pub fn default_registry() -> Arc { manager.register(EndPortalBlock); manager.register(SpawnerBlock); manager.register(EndPortalFrameBlock); + manager.register(RespawnAnchorBlock); manager.register(CandleBlock); manager.register(SeaPickleBlock); manager.register(CakeBlock); @@ -330,6 +335,9 @@ pub fn default_registry() -> Arc { manager.register(LeverBlock); manager.register(LightningRodBlock); manager.register(SculkSensorBlock); + manager.register(SculkVeinBlock); + manager.register(SculkCatalystBlock); + manager.register(SculkShriekerBlock); manager.register(ObserverBlock); manager.register(TripwireBlock); manager.register(TripwireHookBlock);