steel_core/behavior/blocks/falling/
dragon_egg_block.rs1use std::sync::Arc;
2
3use rand::random_range;
4use steel_macros::block_behavior;
5use steel_registry::blocks::BlockRef;
6use steel_registry::blocks::block_state_ext::BlockStateExt;
7use steel_registry::blocks::properties::Direction;
8use steel_utils::types::UpdateFlags;
9use steel_utils::{BlockPos, BlockStateId};
10
11use crate::behavior::{BlockBehavior, BlockPlaceContext, InteractionResult, InventoryAccess};
12use crate::entity::ai::path::PathComputationType;
13use crate::player::Player;
14use crate::world::{LevelAccessor, ScheduledTickAccess, World};
15use steel_registry::items::item::BlockHitResult;
16
17use super::FallingBlock;
18
19const DRAGON_EGG_FALL_DELAY: i32 = 5;
20const DRAGON_EGG_MAX_TELEPORTS: u32 = 1000;
21const DRAGON_EGG_MAX_X: i32 = 16;
22const DRAGON_EGG_MAX_Y: i32 = 8;
23const DRAGON_EGG_MAX_Z: i32 = 16;
24
25#[block_behavior]
27pub struct DragonEggBlock {
28 falling: FallingBlock,
29}
30
31impl DragonEggBlock {
32 #[must_use]
34 pub const fn new(block: BlockRef) -> Self {
35 Self {
36 falling: FallingBlock::new(block),
37 }
38 }
39
40 fn teleport(state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
41 for _ in 0..DRAGON_EGG_MAX_TELEPORTS {
42 let x = random_range(0..DRAGON_EGG_MAX_X) - random_range(0..DRAGON_EGG_MAX_X);
43 let y = random_range(0..DRAGON_EGG_MAX_Y) - random_range(0..DRAGON_EGG_MAX_Y);
44 let z = random_range(0..DRAGON_EGG_MAX_Z) - random_range(0..DRAGON_EGG_MAX_Z);
45
46 let new_pos = BlockPos::new(pos.x() + x, pos.y() + y, pos.z() + z);
47
48 if !world.get_block_state(new_pos).is_air() {
49 continue;
50 }
51
52 if world.get_block_state(new_pos.below()).is_air() {
53 continue;
54 }
55
56 if !world.is_block_within_world_border(new_pos) {
57 continue;
58 }
59
60 if world.is_outside_build_height(new_pos.y()) {
61 continue;
62 }
63
64 world.set_block_state(new_pos, state, UpdateFlags::UPDATE_CLIENTS);
65 world.remove_block(pos, false);
66 break;
67 }
68 }
69}
70
71impl BlockBehavior for DragonEggBlock {
72 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
73 self.falling.get_state_for_placement(context)
74 }
75
76 fn on_place(
77 &self,
78 _state: BlockStateId,
79 world: &Arc<World>,
80 pos: BlockPos,
81 _old_state: BlockStateId,
82 _moved_by_piston: bool,
83 ) {
84 let _ = world.schedule_block_tick_default(pos, self.falling.block(), DRAGON_EGG_FALL_DELAY);
85 }
86
87 fn update_shape(
88 &self,
89 state: BlockStateId,
90 ticks: &dyn ScheduledTickAccess,
91 pos: BlockPos,
92 _direction: Direction,
93 _neighbor_pos: BlockPos,
94 _neighbor_state: BlockStateId,
95 ) -> BlockStateId {
96 let _ = ticks.schedule_block_tick_default(pos, self.falling.block(), DRAGON_EGG_FALL_DELAY);
97 state
98 }
99
100 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
101 BlockBehavior::tick(&self.falling, state, world, pos);
102 }
103
104 fn use_without_item(
105 &self,
106 state: BlockStateId,
107 world: &Arc<World>,
108 pos: BlockPos,
109 _player: &Player,
110 _hit_result: &BlockHitResult,
111 _inv: &mut InventoryAccess,
112 ) -> InteractionResult {
113 Self::teleport(state, world, pos);
114 InteractionResult::Success
115 }
116
117 fn attack(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos, _player: &Player) {
118 Self::teleport(state, world, pos);
119 }
120
121 fn is_pathfindable(
122 &self,
123 _state: BlockStateId,
124 _computation_type: PathComputationType,
125 ) -> bool {
126 false
127 }
128}