steel_core/behavior/items/
flint_and_steel.rs1use crate::behavior::blocks::FireBlock;
4use crate::behavior::context::{InteractionResult, UseOnContext};
5use crate::behavior::item::ItemBehavior;
6use steel_macros::item_behavior;
7use steel_registry::item_stack::ItemStack;
8use steel_registry::sound_event::SoundEventRef;
9use steel_registry::vanilla_block_tags::BlockTag;
10use steel_registry::{
11 blocks::{block_state_ext::BlockStateExt, properties::BlockStateProperties},
12 sound_events, vanilla_game_events,
13};
14use steel_utils::types::UpdateFlags;
15use steel_utils::{BlockPos, BlockStateId};
16
17use crate::entity::Entity;
18use crate::world::game_event::GameEventContext;
19
20#[item_behavior]
22pub struct FlintAndSteelItem;
23
24impl ItemBehavior for FlintAndSteelItem {
25 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
26 let click_pos = context.hit_result.block_pos;
27 let clicked_state = context.world.get_block_state(click_pos);
28 if try_light_block(
29 context,
30 click_pos,
31 clicked_state,
32 &sound_events::ITEM_FLINTANDSTEEL_USE,
33 flint_and_steel_pitch(),
34 ) {
35 let has_infinite_materials = context.player.has_infinite_materials();
36 context
37 .inv
38 .with_item(|item| item.hurt_and_break(1, has_infinite_materials));
39 return InteractionResult::Success;
40 }
41
42 let fire_pos = click_pos.relative(context.hit_result.direction);
43 let forward_dir = context.player.direction_yaw();
44
45 if !FireBlock::can_be_placed_at(context.world, fire_pos, forward_dir) {
46 return InteractionResult::Fail;
47 }
48
49 context.world.play_block_sound(
50 &sound_events::ITEM_FLINTANDSTEEL_USE,
51 fire_pos,
52 1.0,
53 rand::random_range(0.8..1.2),
54 Some(context.player.id()),
55 );
56
57 context.world.set_block(
58 fire_pos,
59 FireBlock::get_state(context.world.as_ref(), fire_pos),
60 UpdateFlags::UPDATE_ALL,
61 );
62 context.world.game_event(
63 &vanilla_game_events::BLOCK_PLACE,
64 click_pos,
65 &GameEventContext::new(Some(context.player), None),
66 );
67
68 let has_infinite_materials = context.player.has_infinite_materials();
69 context
70 .inv
71 .with_item(|item| item.hurt_and_break(1, has_infinite_materials));
72
73 InteractionResult::Success
74 }
75}
76
77#[item_behavior]
79pub struct FireChargeItem;
80
81impl ItemBehavior for FireChargeItem {
82 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
83 let click_pos = context.hit_result.block_pos;
84 let clicked_state = context.world.get_block_state(click_pos);
85 if try_light_block(
86 context,
87 click_pos,
88 clicked_state,
89 &sound_events::ITEM_FIRECHARGE_USE,
90 fire_charge_pitch(),
91 ) {
92 context.inv.with_item(ItemStack::shrink_one);
93 return InteractionResult::Success;
94 }
95
96 let fire_pos = click_pos.relative(context.hit_result.direction);
97 let forward_dir = context.player.direction_yaw();
98
99 if !FireBlock::can_be_placed_at(context.world, fire_pos, forward_dir) {
100 return InteractionResult::Fail;
101 }
102
103 context.world.play_block_sound(
104 &sound_events::ITEM_FIRECHARGE_USE,
105 fire_pos,
106 1.0,
107 fire_charge_pitch(),
108 Some(context.player.id()),
109 );
110
111 context.world.set_block(
112 fire_pos,
113 FireBlock::get_state(context.world.as_ref(), fire_pos),
114 UpdateFlags::UPDATE_ALL,
115 );
116 context.world.game_event(
117 &vanilla_game_events::BLOCK_PLACE,
118 fire_pos,
119 &GameEventContext::new(Some(context.player), None),
120 );
121
122 context.inv.with_item(ItemStack::shrink_one);
123
124 InteractionResult::Success
125 }
126}
127
128fn try_light_block(
129 context: &UseOnContext<'_>,
130 pos: BlockPos,
131 state: BlockStateId,
132 sound: SoundEventRef,
133 pitch: f32,
134) -> bool {
135 if !can_light(state) {
136 return false;
137 }
138
139 context
140 .world
141 .play_block_sound(sound, pos, 1.0, pitch, Some(context.player.id()));
142 context.world.set_block(
143 pos,
144 state.set_value(&BlockStateProperties::LIT, true),
145 UpdateFlags::UPDATE_ALL_IMMEDIATE,
146 );
147 context.world.game_event(
148 &vanilla_game_events::BLOCK_CHANGE,
149 pos,
150 &GameEventContext::new(Some(context.player), None),
151 );
152
153 true
154}
155
156fn can_light(state: BlockStateId) -> bool {
157 let Some(lit) = state.try_get_value(&BlockStateProperties::LIT) else {
158 return false;
159 };
160 if lit {
161 return false;
162 }
163
164 let block = state.get_block();
165 if block.has_tag(&BlockTag::CAMPFIRES) {
166 return state.try_get_value(&BlockStateProperties::WATERLOGGED) == Some(false);
167 }
168
169 if block.has_tag(&BlockTag::CANDLES) {
170 return state.try_get_value(&BlockStateProperties::WATERLOGGED) == Some(false);
171 }
172
173 block.has_tag(&BlockTag::CANDLE_CAKES)
174}
175
176fn flint_and_steel_pitch() -> f32 {
177 rand::random_range(0.8..1.2)
178}
179
180fn fire_charge_pitch() -> f32 {
181 (rand::random::<f32>() - rand::random::<f32>()) * 0.2 + 1.0
182}
183
184#[cfg(test)]
185mod tests {
186 use steel_registry::{
187 blocks::{block_state_ext::BlockStateExt, properties::BlockStateProperties},
188 init_vanilla_registry, vanilla_blocks,
189 };
190
191 use super::can_light;
192
193 #[test]
194 fn can_light_rejects_waterlogged_campfires_and_candles() {
195 init_vanilla_registry();
196
197 let waterlogged_campfire = vanilla_blocks::CAMPFIRE
198 .default_state()
199 .set_value(&BlockStateProperties::LIT, false)
200 .set_value(&BlockStateProperties::WATERLOGGED, true);
201 let dry_campfire =
202 waterlogged_campfire.set_value(&BlockStateProperties::WATERLOGGED, false);
203
204 let waterlogged_candle = vanilla_blocks::CANDLE
205 .default_state()
206 .set_value(&BlockStateProperties::LIT, false)
207 .set_value(&BlockStateProperties::WATERLOGGED, true);
208 let dry_candle = waterlogged_candle.set_value(&BlockStateProperties::WATERLOGGED, false);
209
210 assert!(!can_light(waterlogged_campfire));
211 assert!(can_light(dry_campfire));
212 assert!(!can_light(waterlogged_candle));
213 assert!(can_light(dry_candle));
214 }
215
216 #[test]
217 fn can_light_accepts_unlit_candle_cakes() {
218 init_vanilla_registry();
219
220 let unlit_candle_cake = vanilla_blocks::CANDLE_CAKE
221 .default_state()
222 .set_value(&BlockStateProperties::LIT, false);
223 let lit_candle_cake = unlit_candle_cake.set_value(&BlockStateProperties::LIT, true);
224
225 assert!(can_light(unlit_candle_cake));
226 assert!(!can_light(lit_candle_cake));
227 }
228}