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