steel_core/behavior/items/
standing_and_wall_block_item.rs1use steel_macros::item_behavior;
13use steel_registry::blocks::BlockRef;
14use steel_registry::blocks::block_state_ext::BlockStateExt;
15use steel_registry::blocks::properties::Direction;
16use steel_registry::{REGISTRY, vanilla_game_events};
17use steel_utils::types::UpdateFlags;
18
19use crate::behavior::context::{BlockPlaceContext, InteractionResult, UseOnContext};
20use crate::behavior::{BLOCK_BEHAVIORS, ItemBehavior};
21use crate::entity::Entity;
22use crate::world::game_event::GameEventContext;
23
24#[item_behavior]
34pub struct StandingAndWallBlockItem {
35 #[json_arg(vanilla_blocks, json = "block")]
37 pub standing_block: BlockRef,
38 #[json_arg(vanilla_blocks, json = "wall_block")]
40 pub wall_block: BlockRef,
41 #[json_arg(
44 r#enum = "Direction",
45 module = "steel_registry::blocks::properties",
46 json = "attachment_direction"
47 )]
48 pub attachment_direction: Direction,
49}
50
51impl StandingAndWallBlockItem {
52 #[must_use]
59 pub const fn new(
60 standing_block: BlockRef,
61 wall_block: BlockRef,
62 attachment_direction: Direction,
63 ) -> Self {
64 Self {
65 standing_block,
66 wall_block,
67 attachment_direction,
68 }
69 }
70
71 #[must_use]
76 pub fn get_placement_state(
77 &self,
78 place_context: &BlockPlaceContext<'_>,
79 ) -> Option<steel_utils::BlockStateId> {
80 let block_behaviors = &*BLOCK_BEHAVIORS;
81
82 let wall_state = block_behaviors
84 .get_behavior(self.wall_block)
85 .get_state_for_placement(place_context);
86
87 let directions = place_context.get_nearest_looking_directions();
88 let skip_direction = self.attachment_direction.opposite();
89
90 for direction in directions {
91 if direction == skip_direction {
94 continue;
95 }
96
97 let possible_state = if direction == self.attachment_direction {
99 block_behaviors
101 .get_behavior(self.standing_block)
102 .get_state_for_placement(place_context)
103 } else {
104 wall_state
106 };
107
108 let Some(state) = possible_state else {
109 continue;
110 };
111
112 let collision_shape = state.get_collision_shape_at(place_context.place_pos());
115 if place_context
116 .world
117 .is_unobstructed(collision_shape, place_context.place_pos())
118 {
119 return Some(state);
120 }
121 }
122
123 None
124 }
125
126 #[must_use]
128 pub fn get_block_for_state(&self, state: steel_utils::BlockStateId) -> BlockRef {
129 if REGISTRY
131 .blocks
132 .by_state_id(state)
133 .is_some_and(|b| b.key == self.standing_block.key)
134 {
135 self.standing_block
136 } else {
137 self.wall_block
138 }
139 }
140}
141
142impl ItemBehavior for StandingAndWallBlockItem {
143 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
144 let mut place_context = context.build_place_context();
145 if !place_context.can_place() {
146 return InteractionResult::Fail;
147 }
148 let place_pos = place_context.place_pos();
149
150 let Some(new_state) = self.get_placement_state(&place_context) else {
151 return InteractionResult::Fail;
152 };
153
154 if !context
155 .world
156 .set_block(place_pos, new_state, UpdateFlags::UPDATE_ALL_IMMEDIATE)
157 {
158 return InteractionResult::Fail;
159 }
160 let placed_state = context.world.get_block_state(place_pos);
161
162 let block = self.get_block_for_state(new_state);
163 let sound_type = &block.config.sound_type;
164 context.world.play_block_sound(
165 sound_type.place_sound,
166 place_pos,
167 sound_type.volume,
168 sound_type.pitch,
169 Some(context.player.id()),
170 );
171 context.world.game_event(
172 &vanilla_game_events::BLOCK_PLACE,
173 place_pos,
174 &GameEventContext::new(Some(context.player), Some(placed_state)),
175 );
176
177 place_context.with_item_mut(|item| item.shrink(1));
178
179 InteractionResult::Success
180 }
181}