steel_core/behavior/items/
block_item.rs1use steel_macros::item_behavior;
4use steel_registry::{
5 blocks::{BlockRef, block_state_ext::BlockStateExt},
6 vanilla_blocks, vanilla_game_events,
7};
8use steel_utils::{BlockStateId, types::UpdateFlags};
9
10use crate::behavior::context::{BlockPlaceContext, InteractionResult, UseOnContext};
11use crate::behavior::{BLOCK_BEHAVIORS, ItemBehavior};
12use crate::entity::Entity;
13use crate::fluid::{FluidStateExt as _, get_fluid_state};
14use crate::world::game_event::GameEventContext;
15
16#[item_behavior]
18pub struct BlockItem {
19 #[json_arg(vanilla_blocks, json = "block")]
21 pub block: BlockRef,
22}
23
24impl BlockItem {
25 const PLACE_BLOCK_FLAGS: UpdateFlags = UpdateFlags::UPDATE_ALL_IMMEDIATE;
26
27 #[must_use]
29 pub const fn new(block: BlockRef) -> Self {
30 Self { block }
31 }
32
33 fn place_with(
34 &self,
35 mut context: BlockPlaceContext<'_>,
36 place_block: impl FnOnce(&BlockPlaceContext<'_>, BlockStateId) -> bool,
37 ) -> InteractionResult {
38 if !context.can_place() {
39 return InteractionResult::Fail;
40 }
41 let place_pos = context.place_pos();
42
43 let behavior = BLOCK_BEHAVIORS.get_behavior(self.block);
44 let Some(new_state) = behavior.get_state_for_placement(&context) else {
45 return InteractionResult::Fail;
46 };
47
48 if !behavior.can_survive(new_state, context.world, place_pos) {
49 return InteractionResult::Fail;
50 }
51
52 let collision_shape = new_state.get_collision_shape_at(place_pos);
53 if !context.world.is_unobstructed(collision_shape, place_pos) {
54 return InteractionResult::Fail;
55 }
56
57 if !place_block(&context, new_state) {
58 return InteractionResult::Fail;
59 }
60
61 let placed_state = context.world.get_block_state(place_pos);
62 if placed_state.get_block() == self.block {
63 let placed_behavior = BLOCK_BEHAVIORS.get_behavior(placed_state.get_block());
64 placed_behavior.set_placed_by(placed_state, context.world, place_pos, context.source());
65 }
66
67 let sound_type = &self.block.config.sound_type;
69 context.world.play_block_sound(
70 sound_type.place_sound,
71 place_pos,
72 sound_type.volume,
73 sound_type.pitch,
74 context.player().map(Entity::id),
75 );
76 context.world.game_event(
77 &vanilla_game_events::BLOCK_PLACE,
78 place_pos,
79 &GameEventContext::new(
80 context.player().map(|player| player as &dyn Entity),
81 Some(placed_state),
82 ),
83 );
84
85 context.with_item_mut(|item| item.shrink(1));
86
87 InteractionResult::Success
88 }
89
90 pub fn place(&self, context: BlockPlaceContext<'_>) -> InteractionResult {
92 self.place_with(context, Self::place_block)
93 }
94
95 fn place_block(context: &BlockPlaceContext<'_>, state: BlockStateId) -> bool {
96 context
97 .world
98 .set_block(context.place_pos(), state, Self::PLACE_BLOCK_FLAGS)
99 }
100}
101
102impl ItemBehavior for BlockItem {
103 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
104 self.place(context.build_place_context())
105 }
106}
107
108#[item_behavior]
116pub struct DoubleHighBlockItem {
117 #[json_arg(vanilla_blocks, json = "block")]
118 _block: BlockRef,
119 base: BlockItem,
120}
121
122impl DoubleHighBlockItem {
123 const PREPARE_UPPER_FLAGS: UpdateFlags =
124 UpdateFlags::UPDATE_ALL_IMMEDIATE.union(UpdateFlags::UPDATE_KNOWN_SHAPE);
125
126 #[must_use]
128 pub const fn new(block: BlockRef) -> Self {
129 Self {
130 _block: block,
131 base: BlockItem::new(block),
132 }
133 }
134
135 fn place_block(context: &BlockPlaceContext<'_>, state: BlockStateId) -> bool {
136 let above = context.place_pos().above();
137 let above_state = if get_fluid_state(context.world, above).is_water() {
138 vanilla_blocks::WATER.default_state()
139 } else {
140 vanilla_blocks::AIR.default_state()
141 };
142 let _ = context
143 .world
144 .set_block(above, above_state, Self::PREPARE_UPPER_FLAGS);
145
146 BlockItem::place_block(context, state)
147 }
148}
149
150impl ItemBehavior for DoubleHighBlockItem {
151 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
152 self.base
153 .place_with(context.build_place_context(), Self::place_block)
154 }
155}