mc173/block_entity/
furnace.rs

1//! Furnace block entity.
2
3use glam::IVec3;
4
5use crate::item::{self, ItemStack};
6use crate::world::{World, Event, BlockEntityEvent, BlockEntityStorage, BlockEntityProgress};
7use crate::{smelt, block};
8
9
10#[derive(Debug, Clone, Default)]
11pub struct FurnaceBlockEntity {
12    /// Input stack of the furnace.
13    pub input_stack: ItemStack,
14    /// Item stack for fueling the furnace.
15    pub fuel_stack: ItemStack,
16    /// Output stack of the furnace.
17    pub output_stack: ItemStack,
18    /// Max burn ticks for the current fuel being consumed.
19    pub burn_max_ticks: u16,
20    /// Current burn remaining ticks until a fuel item need to be consumed again.
21    pub burn_remaining_ticks: u16,
22    /// Current ticks count since the current item has been added.
23    pub smelt_ticks: u16,
24    /// Last input stack, used to compare to new one and update the current recipe.
25    last_input_stack: ItemStack,
26    /// Last output stack, used to compare to new one and update the current recipe.
27    last_output_stack: ItemStack,
28    /// If some recipe has been found for the current input stack, this contains the
29    /// future output stack that will be assigned to output stack.
30    active_output_stack: Option<ItemStack>,
31}
32
33impl FurnaceBlockEntity {
34
35    /// Internal function to compute the new recipe depending on the current input item.
36    /// None is returned if the input stack is empty, if no recipe can be found, or if
37    /// the recipe's output do not fit in the output stack.
38    fn find_new_output_stack(&self) -> Option<ItemStack> {
39
40        if self.input_stack.size == 0 {
41            return None;
42        }
43
44        let input_id = self.input_stack.id;
45        let input_damage = self.input_stack.damage;
46        let mut output_stack = smelt::find_smelting_output(input_id, input_damage)?;
47
48        if !self.output_stack.is_empty() {
49            if (self.output_stack.id, self.output_stack.damage) != (output_stack.id, output_stack.damage) {
50                return None;
51            } else if self.output_stack.size + output_stack.size > item::from_id(output_stack.id).max_stack_size {
52                return None;
53            } else {
54                output_stack.size += self.output_stack.size;
55            }
56        }
57
58        Some(output_stack)
59
60    }
61
62    /// Tick the furnace block entity.
63    pub fn tick(&mut self, world: &mut World, pos: IVec3) {
64
65        // If the input stack have changed since last update, get the new recipe.
66        // TODO: Also update of output stack have changed.
67        if self.input_stack != self.last_input_stack || self.output_stack != self.last_output_stack {
68            self.active_output_stack = self.find_new_output_stack();
69            self.last_input_stack = self.input_stack;
70            self.last_output_stack = self.output_stack;
71        }
72
73        let mut smelt_modified = false;
74        let mut fuel_modified = false;
75
76        let initial_burning = self.burn_remaining_ticks != 0;
77        if initial_burning {
78            self.burn_remaining_ticks -= 1;
79            fuel_modified = true;
80        }
81
82        if let Some(active_output_stack) = &self.active_output_stack {
83
84            if self.burn_remaining_ticks == 0 && !self.fuel_stack.is_empty() {
85
86                self.burn_max_ticks = smelt::get_burn_ticks(self.fuel_stack.id);
87                self.burn_remaining_ticks = self.burn_max_ticks;
88                
89                if self.burn_max_ticks != 0 {
90
91                    self.fuel_stack.size -= 1;
92                    fuel_modified = true;
93                    
94                    world.push_event(Event::BlockEntity { 
95                        pos, 
96                        inner: BlockEntityEvent::Storage { 
97                            storage: BlockEntityStorage::FurnaceFuel,
98                            stack: self.fuel_stack,
99                        },
100                    });
101
102                    world.push_event(Event::BlockEntity { 
103                        pos, 
104                        inner: BlockEntityEvent::Progress { 
105                            progress: BlockEntityProgress::FurnaceBurnMaxTime, 
106                            value: self.burn_max_ticks,
107                        }, 
108                    });
109
110                }
111
112            }
113
114            if self.burn_remaining_ticks == 0 {
115                if self.smelt_ticks != 0 {
116                    self.smelt_ticks = 0;
117                    smelt_modified = true;
118                }
119            } else {
120
121                self.smelt_ticks += 1;
122                if self.smelt_ticks == 200 {
123
124                    self.smelt_ticks = 0;
125                    // This should not underflow because if input stack is empty, not
126                    // active output stack can be set.
127                    // NOTE: Modifying both of these will trigger an update of the active 
128                    // output stack on the next tick.
129                    self.input_stack.size -= 1;
130                    self.output_stack = *active_output_stack;
131                    
132                    world.push_event(Event::BlockEntity { 
133                        pos, 
134                        inner: BlockEntityEvent::Storage { 
135                            storage: BlockEntityStorage::FurnaceInput,
136                            stack: self.input_stack,
137                        },
138                    });
139                    
140                    world.push_event(Event::BlockEntity { 
141                        pos, 
142                        inner: BlockEntityEvent::Storage { 
143                            storage: BlockEntityStorage::FurnaceOutput,
144                            stack: self.output_stack,
145                        },
146                    });
147
148                }
149
150                smelt_modified = true;
151
152            }
153            
154        } else if self.smelt_ticks != 0 {
155            self.smelt_ticks = 0;
156            smelt_modified = true;
157        }
158
159        if smelt_modified {
160            world.push_event(Event::BlockEntity { 
161                pos, 
162                inner: BlockEntityEvent::Progress { 
163                    progress: BlockEntityProgress::FurnaceSmeltTime, 
164                    value: self.smelt_ticks,
165                }, 
166            });
167        }
168
169        if fuel_modified {
170            world.push_event(Event::BlockEntity { 
171                pos, 
172                inner: BlockEntityEvent::Progress { 
173                    progress: BlockEntityProgress::FurnaceBurnRemainingTime, 
174                    value: self.burn_remaining_ticks,
175                }, 
176            });
177        }
178
179        if initial_burning != (self.burn_remaining_ticks != 0) {
180            let (_id, metadata) = world.get_block(pos).expect("should not be ticking if not loaded");
181            if initial_burning {
182                // No longer burning.
183                world.set_block_notify(pos, block::FURNACE, metadata);
184            } else {
185                // Now burning.
186                world.set_block_notify(pos, block::FURNACE_LIT, metadata);
187            }
188        }
189
190    }
191
192}