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 has_infinite_materials = context.player.has_infinite_materials();
145 let mut place_context = context.build_place_context();
146 if !place_context.can_place() {
147 return InteractionResult::Fail;
148 }
149 let place_pos = place_context.place_pos();
150
151 let Some(new_state) = self.get_placement_state(&place_context) else {
152 return InteractionResult::Fail;
153 };
154
155 if !context
156 .world
157 .set_block(place_pos, new_state, UpdateFlags::UPDATE_ALL_IMMEDIATE)
158 {
159 return InteractionResult::Fail;
160 }
161 let placed_state = context.world.get_block_state(place_pos);
162
163 let block = self.get_block_for_state(new_state);
164 let sound_type = &block.config.sound_type;
165 context.world.play_block_sound(
166 sound_type.place_sound,
167 place_pos,
168 sound_type.volume,
169 sound_type.pitch,
170 Some(context.player.id()),
171 );
172 context.world.game_event(
173 &vanilla_game_events::BLOCK_PLACE,
174 place_pos,
175 &GameEventContext::new(Some(context.player), Some(placed_state)),
176 );
177
178 place_context.with_item_mut(|item| item.consume_one(has_infinite_materials));
179
180 InteractionResult::Success
181 }
182}