mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: impl some Sculk blocks
This commit is contained in:
@@ -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;
|
||||
|
||||
119
crates/pumpkin/src/block/blocks/respawn_anchor.rs
Normal file
119
crates/pumpkin/src/block/blocks/respawn_anchor.rs
Normal file
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
3
crates/pumpkin/src/block/blocks/sculk/mod.rs
Normal file
3
crates/pumpkin/src/block/blocks/sculk/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod sculk_catalyst;
|
||||
pub mod sculk_shrieker;
|
||||
pub mod sculk_vein;
|
||||
23
crates/pumpkin/src/block/blocks/sculk/sculk_catalyst.rs
Normal file
23
crates/pumpkin/src/block/blocks/sculk/sculk_catalyst.rs
Normal file
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
110
crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs
Normal file
110
crates/pumpkin/src/block/blocks/sculk/sculk_shrieker.rs
Normal file
@@ -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<World>, 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::<SculkShriekerBlockEntity>()
|
||||
{
|
||||
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;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
227
crates/pumpkin/src/block/blocks/sculk/sculk_vein.rs
Normal file
227
crates/pumpkin/src/block/blocks/sculk/sculk_vein.rs
Normal file
@@ -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<BlockDirection>, 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<BlockDirection> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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<BlockRegistry> {
|
||||
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<BlockRegistry> {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user