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 mut place_context = context.build_place_context();
72 if !place_context.can_place() {
73 return InteractionResult::Fail;
74 }
75 let place_pos = place_context.place_pos();
76
77 let Some(new_state) = self.inner.get_placement_state(&place_context) else {
78 return InteractionResult::Fail;
79 };
80
81 if !context
82 .world
83 .set_block(place_pos, new_state, UpdateFlags::UPDATE_ALL_IMMEDIATE)
84 {
85 return InteractionResult::Fail;
86 }
87 let placed_state = context.world.get_block_state(place_pos);
88
89 let block = self.inner.get_block_for_state(new_state);
90 let sound_type = &block.config.sound_type;
91 context.world.play_block_sound(
92 sound_type.place_sound,
93 place_pos,
94 sound_type.volume,
95 sound_type.pitch,
96 Some(context.player.id()),
97 );
98 context.world.game_event(
99 &vanilla_game_events::BLOCK_PLACE,
100 place_pos,
101 &GameEventContext::new(Some(context.player), Some(placed_state)),
102 );
103
104 place_context.with_item_mut(|item| item.shrink(1));
105
106 context.player.open_sign_editor(place_pos, true);
108
109 InteractionResult::Success
110 }
111}
112
113#[item_behavior]
117pub struct HangingSignItem {
118 #[json_arg(vanilla_blocks, json = "block")]
120 pub ceiling_block: BlockRef,
121 #[json_arg(vanilla_blocks, json = "wall_block")]
123 pub wall_block: BlockRef,
124}
125
126impl HangingSignItem {
127 #[must_use]
129 pub const fn new(ceiling_block: BlockRef, wall_block: BlockRef) -> Self {
130 Self {
131 ceiling_block,
132 wall_block,
133 }
134 }
135}
136
137fn can_attach_to(
141 world: &Arc<World>,
142 sign_facing: Direction,
143 attach_pos: BlockPos,
144 attach_face: Direction,
145) -> bool {
146 let attach_state = world.get_block_state(attach_pos);
147 let attach_block = REGISTRY.blocks.by_state_id(attach_state);
148
149 if let Some(block) = attach_block
150 && block.has_tag(&BlockTag::WALL_HANGING_SIGNS)
151 {
152 if let Some(neighbor_facing) =
154 attach_state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING)
155 {
156 return neighbor_facing.axis() == sign_facing.axis();
157 }
158 }
159
160 world.is_face_sturdy_for(attach_state, attach_pos, attach_face, SupportType::Full)
162}
163
164fn can_wall_hanging_sign_place(world: &Arc<World>, state: BlockStateId, pos: BlockPos) -> bool {
169 let Some(facing) = state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING) else {
170 return false;
171 };
172
173 let clockwise = facing.rotate_y_clockwise();
174 let counter_clockwise = facing.rotate_y_counter_clockwise();
175
176 let can_attach_clockwise = {
177 let attach_pos = clockwise.relative(pos);
178 can_attach_to(world, facing, attach_pos, counter_clockwise)
179 };
180
181 let can_attach_counter = {
182 let attach_pos = counter_clockwise.relative(pos);
183 can_attach_to(world, facing, attach_pos, clockwise)
184 };
185
186 can_attach_clockwise || can_attach_counter
187}
188
189fn can_place_hanging_sign(world: &Arc<World>, state: BlockStateId, pos: BlockPos) -> bool {
194 let block = REGISTRY.blocks.by_state_id(state);
195
196 if let Some(block) = block
198 && block.has_tag(&BlockTag::WALL_HANGING_SIGNS)
199 && !can_wall_hanging_sign_place(world, state, pos)
200 {
201 return false;
202 }
203
204 true
206}
207
208impl ItemBehavior for HangingSignItem {
209 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
210 let mut place_context = context.build_place_context();
211 if !place_context.can_place() {
212 return InteractionResult::Fail;
213 }
214 let place_pos = place_context.place_pos();
215
216 let block_behaviors = &*BLOCK_BEHAVIORS;
217
218 let blocks_to_try = if context.hit_result.direction == Direction::Down {
220 [self.ceiling_block, self.wall_block]
221 } else {
222 [self.wall_block, self.ceiling_block]
223 };
224
225 let mut new_state = None;
226 let mut placed_block = None;
227 for block in blocks_to_try {
228 let behavior = block_behaviors.get_behavior(block);
229 let Some(state) = behavior.get_state_for_placement(&place_context) else {
230 continue;
231 };
232
233 if !can_place_hanging_sign(context.world, state, place_pos) {
235 continue;
236 }
237
238 let collision_shape = state.get_collision_shape_at(place_pos);
239 if context.world.is_unobstructed(collision_shape, place_pos) {
240 new_state = Some(state);
241 placed_block = Some(block);
242 break;
243 }
244 }
245
246 let Some(state) = new_state else {
247 return InteractionResult::Fail;
248 };
249
250 if !context
251 .world
252 .set_block(place_pos, state, UpdateFlags::UPDATE_ALL_IMMEDIATE)
253 {
254 return InteractionResult::Fail;
255 }
256 let placed_state = context.world.get_block_state(place_pos);
257
258 if let Some(block) = placed_block {
259 let sound_type = &block.config.sound_type;
260 context.world.play_block_sound(
261 sound_type.place_sound,
262 place_pos,
263 sound_type.volume,
264 sound_type.pitch,
265 Some(context.player.id()),
266 );
267 }
268 context.world.game_event(
269 &vanilla_game_events::BLOCK_PLACE,
270 place_pos,
271 &GameEventContext::new(Some(context.player), Some(placed_state)),
272 );
273
274 place_context.with_item_mut(|item| item.shrink(1));
275
276 context.player.open_sign_editor(place_pos, true);
278
279 InteractionResult::Success
280 }
281}