Skip to main content

steel_core/block_entity/entities/
daylight_detector.rs

1//! Stateless ticking storage for daylight detectors.
2
3use std::sync::{Arc, Weak};
4
5use simdnbt::borrow::BaseNbtCompound as BorrowedNbtCompound;
6use simdnbt::owned::NbtCompound;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::blocks::properties::BlockStateProperties;
9use steel_registry::vanilla_block_entity_types;
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey};
12
13use crate::behavior::blocks::DaylightDetectorBlock;
14use crate::block_entity::{BlockEntity, BlockEntityBase};
15use crate::world::World;
16
17/// Vanilla `DaylightDetectorBlockEntity`.
18pub struct DaylightDetectorBlockEntity {
19    base: BlockEntityBase,
20}
21
22// SAFETY: This key is owned by Steel and uniquely identifies
23// `DaylightDetectorBlockEntity`.
24unsafe impl DowncastType for DaylightDetectorBlockEntity {
25    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/daylight_detector");
26}
27
28impl DaylightDetectorBlockEntity {
29    /// Creates daylight-detector ticking storage.
30    #[must_use]
31    pub fn new(world: Weak<World>, pos: BlockPos, state: BlockStateId) -> Self {
32        Self {
33            base: BlockEntityBase::new(
34                &vanilla_block_entity_types::DAYLIGHT_DETECTOR,
35                world,
36                pos,
37                state,
38            ),
39        }
40    }
41}
42
43impl BlockEntity for DaylightDetectorBlockEntity {
44    fn base(&self) -> &BlockEntityBase {
45        &self.base
46    }
47
48    fn load_additional(&self, _nbt: &BorrowedNbtCompound<'_>) {}
49
50    fn save_additional(&self, _nbt: &mut NbtCompound) {}
51
52    fn tick(&self, world: &Arc<World>) {
53        if world.game_time() % 20 != 0 {
54            return;
55        }
56
57        let pos = self.get_block_pos();
58        let state = self.get_block_state();
59        let target = DaylightDetectorBlock::signal_strength(world, pos, state);
60        if state.get_value(&BlockStateProperties::POWER) == target {
61            return;
62        }
63
64        world.set_block(
65            pos,
66            state.set_value(&BlockStateProperties::POWER, target),
67            UpdateFlags::UPDATE_ALL,
68        );
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use steel_registry::init_vanilla_registry;
75    use steel_registry::{vanilla_blocks, vanilla_world_clocks};
76    use steel_utils::ChunkPos;
77
78    use super::*;
79    use crate::behavior::init_behaviors;
80    use crate::test_support::{fresh_test_world, insert_ready_full_chunk};
81
82    #[test]
83    fn detector_updates_only_on_vanilla_twenty_game_tick_cadence() {
84        init_vanilla_registry();
85        init_behaviors();
86        let world = fresh_test_world("daylight_detector_cadence");
87        assert_eq!(
88            world.set_clock_total_ticks(&vanilla_world_clocks::OVERWORLD, 18_000),
89            Some(())
90        );
91        let pos = BlockPos::new(4, 64, 4);
92        insert_ready_full_chunk(&world, ChunkPos::from_block_pos(pos));
93        let state = vanilla_blocks::DAYLIGHT_DETECTOR
94            .default_state()
95            .set_value(&BlockStateProperties::INVERTED, true);
96        assert!(world.set_block(pos, state, UpdateFlags::UPDATE_ALL));
97        let detector = DaylightDetectorBlockEntity::new(Arc::downgrade(&world), pos, state);
98
99        detector.tick(&world);
100        assert_eq!(
101            world
102                .get_block_state(pos)
103                .get_value(&BlockStateProperties::POWER),
104            11
105        );
106
107        world.level_data.write().set_game_time(1);
108        assert!(world.set_block(pos, state, UpdateFlags::UPDATE_ALL));
109        detector.tick(&world);
110        assert_eq!(
111            world
112                .get_block_state(pos)
113                .get_value(&BlockStateProperties::POWER),
114            0
115        );
116    }
117}