mc173/world/
power.rs

1//! Redstone power calculations. The behavior of each power-producing block is described
2//! in this module.
3
4use glam::IVec3;
5
6use crate::geom::{Face, FaceSet};
7use crate::block;
8
9use super::World;
10
11
12/// Methods related to redstone power calculation in the world.
13impl World {
14
15    /// Check if the given block position get any active power from surrounding faces.
16    #[inline]
17    pub fn has_active_power(&mut self, pos: IVec3) -> bool {
18        Face::ALL.into_iter().any(|face| self.has_active_power_from(pos + face.delta(), face.opposite()))
19    }
20
21    /// Check if the given block position get any passive power from surrounding faces.
22    #[inline]
23    pub fn has_passive_power(&mut self, pos: IVec3) -> bool {
24        Face::ALL.into_iter().any(|face| self.has_passive_power_from(pos + face.delta(), face.opposite()))
25    }
26
27    /// Return true if the given block's face produces any active power.
28    #[inline]
29    pub fn has_active_power_from(&mut self, pos: IVec3, face: Face) -> bool {
30        self.get_active_power_from(pos, face) > 0
31    }
32
33    /// Return true if the given block's face has any passive power.
34    #[inline]
35    pub fn has_passive_power_from(&mut self, pos: IVec3, face: Face) -> bool {
36        self.get_passive_power_from(pos, face) > 0
37    }
38
39    /// Get the active power produced by a block's face.
40    pub fn get_active_power_from(&mut self, pos: IVec3, face: Face) -> u8 {
41        let power = self.get_power_from(pos, face, true);
42        if power.indirect || !power.passive {
43            power.level
44        } else {
45            0
46        }
47    }
48
49    /// Get the passive power of a block's face.
50    pub fn get_passive_power_from(&mut self, pos: IVec3, face: Face) -> u8 {
51        self.get_power_from(pos, face, true).level
52    }
53
54    /// Get the power produced by a block on a given face.
55    fn get_power_from(&mut self, pos: IVec3, face: Face, test_block: bool) -> Power {
56
57        let Some((id, metadata)) = self.get_block(pos) else { return Power::OFF };
58
59        match id {
60            block::LEVER => self.get_lever_power_from(face, metadata),
61            block::BUTTON => self.get_button_power_from(face, metadata),
62            block::REPEATER_LIT => self.get_repeater_power_from(face, metadata),
63            block::REDSTONE_TORCH_LIT => self.get_redstone_torch_power_from(face, metadata),
64            block::REDSTONE => self.get_redstone_power_from(pos, face, metadata),
65            // Opaque block relaying indirect power 
66            _ if test_block && block::material::is_opaque_cube(id) => 
67                self.get_block_power_from(pos, face),
68            // Non-redstone blocks
69            _ => Power::OFF
70        }
71
72    }
73
74    /// Get the power of a block that would be indirectly powered.
75    fn get_block_power_from(&mut self, pos: IVec3, face: Face) -> Power {
76
77        // By default the block is passive, but if a face has a non-passive power then is 
78        // will no longer be passive.
79        let mut ret = Power { level: 0, indirect: false, passive: true };
80
81        // Find the maximum 
82        for test_face in [Face::NegY, Face::PosY, Face::NegZ, Face::PosZ, Face::NegX, Face::PosX] {
83            if test_face != face {
84
85                // Test the power coming from that face, but disable 'test_block' to avoid
86                // infinite recursion between those two functions, this assumption is valid
87                // because a block cannot retransmit other block's power.
88                let power = self.get_power_from(pos + test_face.delta(), test_face.opposite(), false);
89                // Only relay the power if the face provides indirect power.
90                if power.indirect {
91
92                    if !power.passive && ret.passive {
93                        ret.level = power.level;
94                        ret.passive = false;
95                    } else if power.passive == ret.passive && power.level > ret.level {
96                        ret.level = power.level;
97                    }
98
99                    // If return value is not passive and already maximum level, return.
100                    if !ret.passive && ret.level >= 15 {
101                        break;
102                    }
103
104                }
105
106            }
107        }
108
109        ret
110
111    }
112
113    fn get_lever_power_from(&mut self, face: Face, metadata: u8) -> Power {
114        if block::lever::is_active(metadata) {
115            if block::lever::get_face(metadata).map(|(f, _)| f) == Some(face) {
116                Power::ON_INDIRECT
117            } else {
118                Power::ON_DIRECT
119            }
120        } else {
121            Power::OFF
122        }
123    }
124
125    fn get_button_power_from(&mut self, face: Face, metadata: u8) -> Power {
126        if block::button::is_active(metadata) {
127            if block::button::get_face(metadata) == Some(face) {
128                Power::ON_INDIRECT
129            } else {
130                Power::ON_DIRECT
131            }
132        } else {
133            Power::OFF
134        }
135    }
136
137    fn get_repeater_power_from(&mut self, face: Face, metadata: u8) -> Power {
138        if block::repeater::get_face(metadata) == face {
139            Power::ON_INDIRECT
140        } else {
141            Power::OFF
142        }
143    }
144
145    fn get_redstone_torch_power_from(&mut self, face: Face, metadata: u8) -> Power {
146        if block::torch::get_face(metadata) == Some(face) {
147            Power::OFF
148        } else if face == Face::PosY {
149            Power::ON_INDIRECT
150        } else {
151            Power::ON_DIRECT
152        }
153    }
154
155    fn get_redstone_power_from(&mut self, pos: IVec3, face: Face, metadata: u8) -> Power {
156        if face == Face::PosY || metadata == 0 {
157            Power::OFF
158        } else if face == Face::NegY {
159            Power { level: metadata, indirect: true, passive: true }
160        } else {
161
162            let mut links = FaceSet::new();
163
164            let opaque_above = self.get_block(pos + IVec3::Y)
165                .map(|(above_id, _)| block::material::is_opaque_cube(above_id))
166                .unwrap_or(true);
167
168            for face in [Face::NegX, Face::PosX, Face::NegZ, Face::PosZ] {
169                let face_pos = pos + face.delta();
170                if self.is_linkable_from(face_pos, face.opposite()) {
171                    links.insert(face);
172                } else {
173                    if let Some((id, _)) = self.get_block(face_pos) {
174                        if !block::material::is_opaque_cube(id) {
175                            if self.is_linkable_from(face_pos - IVec3::Y, Face::PosY) {
176                                links.insert(face);
177                            }
178                        } else if !opaque_above {
179                            if self.is_linkable_from(face_pos + IVec3::Y, Face::NegY) {
180                                links.insert(face);
181                            }
182                        }
183                    }
184                }
185            }
186
187            // Check if the redstone wire is directly pointing to its horizontal faces,
188            // if so the current is indirect and can be transmitted through the face block,
189            // if not it is just a passive signal that can be detected by repeaters.
190            let indirect = if links.is_empty() {
191                // The redstone wire has no links, so it has a cross shape and provide power
192                // to all sides.
193                true
194            } else {
195                match face {
196                    Face::NegZ => links.contains(Face::PosZ) && !links.contains_x(),
197                    Face::PosZ => links.contains(Face::NegZ) && !links.contains_x(),
198                    Face::NegX => links.contains(Face::PosX) && !links.contains_z(),
199                    Face::PosX => links.contains(Face::NegX) && !links.contains_z(),
200                    _ => unreachable!()
201                }
202            };
203
204            Power { level: metadata, indirect, passive: true }
205
206        }
207    }
208
209    /// Return true if the block at given position can link to a redstone wire from its 
210    /// given face.
211    fn is_linkable_from(&mut self, pos: IVec3, face: Face) -> bool {
212        if let Some((id, metadata)) = self.get_block(pos) {
213            match id {
214                block::LEVER |
215                block::BUTTON |
216                block::DETECTOR_RAIL |
217                block::WOOD_PRESSURE_PLATE |
218                block::STONE_PRESSURE_PLATE |
219                block::REDSTONE_TORCH |
220                block::REDSTONE_TORCH_LIT |
221                block::REDSTONE => true,
222                block::REPEATER |
223                block::REPEATER_LIT => {
224                    let repeater_face = block::repeater::get_face(metadata);
225                    face == repeater_face.opposite()
226                }
227                _ => false
228            }
229        } else {
230            false
231        }
232    }
233
234}
235
236
237/// Internal structure describing the properties of a redstone power signal.
238#[derive(Debug)]
239struct Power {
240    /// The redstone power level (0..16).
241    level: u8,
242    /// If this power can be relayed indirectly by opaque blocks.
243    indirect: bool,
244    /// If this power is passive.
245    passive: bool,
246}
247
248impl Power {
249
250    const OFF: Self = Self { level: 0, indirect: false, passive: false };
251    const ON_INDIRECT: Self = Self { level: 15, indirect: true, passive: false };
252    const ON_DIRECT: Self = Self { level: 15, indirect: false, passive: false };
253
254}