steel_core/behavior/items/
sign_item.rs1use std::sync::Arc;
10use steel_macros::item_behavior;
11use steel_registry::REGISTRY;
12use steel_registry::blocks::BlockRef;
13use steel_registry::blocks::block_state_ext::BlockStateExt;
14use steel_registry::blocks::properties::{BlockStateProperties, Direction};
15use steel_registry::blocks::shapes::SupportType;
16use steel_registry::vanilla_block_tags::BlockTag;
17use steel_registry::vanilla_game_events;
18use steel_utils::types::UpdateFlags;
19use steel_utils::{BlockPos, BlockStateId};
20
21use super::standing_and_wall_block_item::StandingAndWallBlockItem;
22use crate::behavior::context::{InteractionResult, UseOnContext};
23use crate::behavior::{BLOCK_BEHAVIORS, ItemBehavior};
24use crate::entity::Entity;
25use crate::world::game_event::GameEventContext;
26use crate::world::{LevelReader as _, World};
27
28#[item_behavior]
37pub struct SignItem {
38 #[json_arg(vanilla_blocks, json = "block")]
39 _standing_block: BlockRef,
40 #[json_arg(vanilla_blocks, json = "wall_block")]
41 _wall_block: BlockRef,
42 #[json_arg(
43 r#enum = "Direction",
44 module = "steel_registry::blocks::properties",
45 json = "attachment_direction"
46 )]
47 _attachment_direction: Direction,
48 inner: StandingAndWallBlockItem,
50}
51
52impl SignItem {
53 #[must_use]
55 pub const fn new(
56 standing_block: BlockRef,
57 wall_block: BlockRef,
58 attachment_direction: Direction,
59 ) -> Self {
60 Self {
61 _standing_block: standing_block,
62 _wall_block: wall_block,
63 _attachment_direction: attachment_direction,
64 inner: StandingAndWallBlockItem::new(standing_block, wall_block, attachment_direction),
65 }
66 }
67}
68
69impl ItemBehavior for SignItem {
70 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
71 let has_infinite_materials = context.player.has_infinite_materials();
72 let mut place_context = context.build_place_context();
73 if !place_context.can_place() {
74 return InteractionResult::Fail;
75 }
76 let place_pos = place_context.place_pos();
77
78 let Some(new_state) = self.inner.get_placement_state(&place_context) else {
79 return InteractionResult::Fail;
80 };
81
82 if !context
83 .world
84 .set_block(place_pos, new_state, UpdateFlags::UPDATE_ALL_IMMEDIATE)
85 {
86 return InteractionResult::Fail;
87 }
88 let placed_state = context.world.get_block_state(place_pos);
89
90 let block = self.inner.get_block_for_state(new_state);
91 let sound_type = &block.config.sound_type;
92 context.world.play_block_sound(
93 sound_type.place_sound,
94 place_pos,
95 sound_type.volume,
96 sound_type.pitch,
97 Some(context.player.id()),
98 );
99 context.world.game_event(
100 &vanilla_game_events::BLOCK_PLACE,
101 place_pos,
102 &GameEventContext::new(Some(context.player), Some(placed_state)),
103 );
104
105 place_context.with_item_mut(|item| item.consume_one(has_infinite_materials));
106
107 context.player.open_sign_editor(place_pos, true);
109
110 InteractionResult::Success
111 }
112}
113
114#[item_behavior]
118pub struct HangingSignItem {
119 #[json_arg(vanilla_blocks, json = "block")]
121 pub ceiling_block: BlockRef,
122 #[json_arg(vanilla_blocks, json = "wall_block")]
124 pub wall_block: BlockRef,
125}
126
127impl HangingSignItem {
128 #[must_use]
130 pub const fn new(ceiling_block: BlockRef, wall_block: BlockRef) -> Self {
131 Self {
132 ceiling_block,
133 wall_block,
134 }
135 }
136}
137
138fn can_attach_to(
142 world: &Arc<World>,
143 sign_facing: Direction,
144 attach_pos: BlockPos,
145 attach_face: Direction,
146) -> bool {
147 let attach_state = world.get_block_state(attach_pos);
148 let attach_block = REGISTRY.blocks.by_state_id(attach_state);
149
150 if let Some(block) = attach_block
151 && block.has_tag(&BlockTag::WALL_HANGING_SIGNS)
152 {
153 if let Some(neighbor_facing) =
155 attach_state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING)
156 {
157 return neighbor_facing.axis() == sign_facing.axis();
158 }
159 }
160
161 world.is_face_sturdy_for(attach_state, attach_pos, attach_face, SupportType::Full)
163}
164
165fn can_wall_hanging_sign_place(world: &Arc<World>, state: BlockStateId, pos: BlockPos) -> bool {
170 let Some(facing) = state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING) else {
171 return false;
172 };
173
174 let clockwise = facing.rotate_y_clockwise();
175 let counter_clockwise = facing.rotate_y_counter_clockwise();
176
177 let can_attach_clockwise = {
178 let attach_pos = clockwise.relative(pos);
179 can_attach_to(world, facing, attach_pos, counter_clockwise)
180 };
181
182 let can_attach_counter = {
183 let attach_pos = counter_clockwise.relative(pos);
184 can_attach_to(world, facing, attach_pos, clockwise)
185 };
186
187 can_attach_clockwise || can_attach_counter
188}
189
190fn can_place_hanging_sign(world: &Arc<World>, state: BlockStateId, pos: BlockPos) -> bool {
195 let block = REGISTRY.blocks.by_state_id(state);
196
197 if let Some(block) = block
199 && block.has_tag(&BlockTag::WALL_HANGING_SIGNS)
200 && !can_wall_hanging_sign_place(world, state, pos)
201 {
202 return false;
203 }
204
205 true
207}
208
209impl ItemBehavior for HangingSignItem {
210 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
211 let has_infinite_materials = context.player.has_infinite_materials();
212 let mut place_context = context.build_place_context();
213 if !place_context.can_place() {
214 return InteractionResult::Fail;
215 }
216 let place_pos = place_context.place_pos();
217
218 let block_behaviors = &*BLOCK_BEHAVIORS;
219
220 let blocks_to_try = if context.hit_result.direction == Direction::Down {
222 [self.ceiling_block, self.wall_block]
223 } else {
224 [self.wall_block, self.ceiling_block]
225 };
226
227 let mut new_state = None;
228 let mut placed_block = None;
229 for block in blocks_to_try {
230 let behavior = block_behaviors.get_behavior(block);
231 let Some(state) = behavior.get_state_for_placement(&place_context) else {
232 continue;
233 };
234
235 if !can_place_hanging_sign(context.world, state, place_pos) {
237 continue;
238 }
239
240 let collision_shape = state.get_collision_shape_at(place_pos);
241 if context.world.is_unobstructed(collision_shape, place_pos) {
242 new_state = Some(state);
243 placed_block = Some(block);
244 break;
245 }
246 }
247
248 let Some(state) = new_state else {
249 return InteractionResult::Fail;
250 };
251
252 if !context
253 .world
254 .set_block(place_pos, state, UpdateFlags::UPDATE_ALL_IMMEDIATE)
255 {
256 return InteractionResult::Fail;
257 }
258 let placed_state = context.world.get_block_state(place_pos);
259
260 if let Some(block) = placed_block {
261 let sound_type = &block.config.sound_type;
262 context.world.play_block_sound(
263 sound_type.place_sound,
264 place_pos,
265 sound_type.volume,
266 sound_type.pitch,
267 Some(context.player.id()),
268 );
269 }
270 context.world.game_event(
271 &vanilla_game_events::BLOCK_PLACE,
272 place_pos,
273 &GameEventContext::new(Some(context.player), Some(placed_state)),
274 );
275
276 place_context.with_item_mut(|item| item.consume_one(has_infinite_materials));
277
278 context.player.open_sign_editor(place_pos, true);
280
281 InteractionResult::Success
282 }
283}