all_is_cubes/block/modifier/
quote.rs

1use crate::block;
2use crate::math::Rgb;
3use crate::universe;
4
5/// Data for [`Modifier::Quote`](block::Modifier::Quote).
6/// Suppresses all behaviors of the [`Block`](block::Block) that might affect the space
7/// around it, (or itself).
8#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
9#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
10#[non_exhaustive]
11pub struct Quote {
12    /// If true, also suppress light and sound effects.
13    pub suppress_ambient: bool,
14}
15
16impl Quote {
17    /// Construct an instance of [`Quote`], the same as [`Quote::default()`].
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    pub(in crate::block) fn evaluate(
23        &self,
24        value: block::MinEval,
25        filter: &block::EvalFilter<'_>,
26    ) -> Result<block::MinEval, block::InEvalError> {
27        // Fully destructure so we don't forget anything.
28        let &Quote { suppress_ambient } = self;
29        let (
30            block::BlockAttributes {
31                display_name,
32                selectable,
33                inventory,
34                mut ambient_sound,
35                rotation_rule,
36                mut placement_action,
37                mut tick_action,
38                mut activation_action,
39                animation_hint,
40            },
41            mut voxels,
42        ) = value.into_parts();
43
44        if !filter.skip_eval {
45            // All `Operation`s must be disabled.
46            tick_action = None;
47            placement_action = None;
48            activation_action = None;
49
50            // If `suppress_ambient`, then avoid the block having any light or sound emission.
51            if suppress_ambient {
52                block::Budget::decrement_voxels(&filter.budget, voxels.count())?;
53                for voxel in voxels.as_vol_mut().as_linear_mut().iter_mut() {
54                    voxel.emission = Rgb::ZERO;
55                }
56
57                ambient_sound = crate::sound::Ambient::SILENT;
58            }
59        }
60
61        Ok(block::MinEval::new(
62            block::BlockAttributes {
63                display_name,
64                selectable,
65                inventory,
66                ambient_sound,
67                rotation_rule,
68                placement_action,
69                tick_action,
70                activation_action,
71                animation_hint,
72            },
73            voxels,
74        ))
75    }
76}
77
78impl From<Quote> for block::Modifier {
79    fn from(value: Quote) -> Self {
80        block::Modifier::Quote(value)
81    }
82}
83
84impl universe::VisitHandles for Quote {
85    fn visit_handles(&self, _visitor: &mut dyn universe::HandleVisitor) {
86        let Quote {
87            suppress_ambient: _,
88        } = self;
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::block::Block;
96    use crate::math::Rgba;
97    use crate::{op, time};
98    use pretty_assertions::assert_eq;
99
100    #[test]
101    fn quote_evaluation() {
102        let light = Rgb::new(1.0, 2.0, 3.0);
103
104        let block = Block::builder()
105            .color(Rgba::WHITE)
106            .light_emission(light)
107            .placement_action(block::PlacementAction {
108                operation: op::Operation::Become(block::AIR),
109                in_front: true,
110            })
111            .tick_action(block::TickAction {
112                operation: op::Operation::Become(block::AIR),
113                schedule: time::Schedule::EVERY_TICK,
114            })
115            .activation_action(op::Operation::Become(block::AIR))
116            .build();
117
118        assert_eq!(
119            eval_without_metadata(&block.clone().with_modifier(Quote {
120                suppress_ambient: false,
121            })),
122            eval_without_metadata(
123                &Block::builder().color(Rgba::WHITE).light_emission(light).build()
124            ),
125            "suppress_ambient = false"
126        );
127
128        assert_eq!(
129            eval_without_metadata(&block.with_modifier(Quote {
130                suppress_ambient: true,
131            })),
132            eval_without_metadata(&Block::builder().color(Rgba::WHITE).build()),
133            "suppress_ambient = true"
134        );
135    }
136
137    // TODO: there should be a less one-off way to express this narrowing of EvaluatedBlock
138    // to only the data and not the metadata (cost and original block).
139    // `MinEval` is not quite it.
140    #[inline(never)]
141    fn eval_without_metadata(block: &Block) -> (block::BlockAttributes, block::Evoxels) {
142        let evaluated = block.evaluate(universe::ReadTicket::stub()).unwrap();
143        (evaluated.attributes().clone(), evaluated.voxels().clone())
144    }
145}