Skip to main content

steel_core/block_entity/entities/
beehive.rs

1//! Beehive block entity implementation.
2
3use std::sync::Weak;
4
5use simdnbt::borrow::{BaseNbtCompound as BorrowedNbtCompound, NbtCompound as NbtCompoundView};
6use simdnbt::owned::{NbtCompound, NbtList};
7use steel_registry::{vanilla_block_entity_types, vanilla_entities};
8use steel_utils::nbt::NbtNumeric as _;
9use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey, locks::SyncMutex};
10
11use crate::block_entity::{BlockEntity, BlockEntityBase};
12use crate::world::World;
13
14/// Maximum number of occupants in a vanilla beehive.
15pub const BEEHIVE_MAX_OCCUPANTS: usize = 3;
16/// Minimum occupation time for bees without nectar.
17pub const BEEHIVE_MIN_OCCUPATION_TICKS_NECTARLESS: i32 = 600;
18
19struct BeeOccupant {
20    entity_data: NbtCompound,
21    ticks_in_hive: i32,
22    min_ticks_in_hive: i32,
23}
24
25impl BeeOccupant {
26    fn worldgen(ticks_in_hive: i32) -> Self {
27        Self {
28            entity_data: default_bee_entity_data(),
29            ticks_in_hive,
30            min_ticks_in_hive: BEEHIVE_MIN_OCCUPATION_TICKS_NECTARLESS,
31        }
32    }
33
34    fn load(nbt: NbtCompoundView<'_, '_>) -> Self {
35        let entity_data = nbt
36            .compound("entity_data")
37            .map_or_else(default_bee_entity_data, |entity_data| {
38                entity_data.to_owned()
39            });
40        let ticks_in_hive = nbt
41            .get("ticks_in_hive")
42            .and_then(|tag| tag.codec_i32())
43            .unwrap_or(0);
44        let min_ticks_in_hive = nbt
45            .get("min_ticks_in_hive")
46            .and_then(|tag| tag.codec_i32())
47            .unwrap_or(BEEHIVE_MIN_OCCUPATION_TICKS_NECTARLESS);
48
49        Self {
50            entity_data,
51            ticks_in_hive,
52            min_ticks_in_hive,
53        }
54    }
55
56    fn save(&self) -> NbtCompound {
57        let mut nbt = NbtCompound::new();
58        nbt.insert("entity_data", self.entity_data.clone());
59        nbt.insert("ticks_in_hive", self.ticks_in_hive);
60        nbt.insert("min_ticks_in_hive", self.min_ticks_in_hive);
61        nbt
62    }
63}
64
65fn default_bee_entity_data() -> NbtCompound {
66    let mut entity_data = NbtCompound::new();
67    entity_data.insert("id", vanilla_entities::BEE.key.to_string());
68    entity_data
69}
70
71struct BeehiveState {
72    stored: Vec<BeeOccupant>,
73}
74
75impl BeehiveState {
76    fn push_occupant(&mut self, occupant: BeeOccupant) -> bool {
77        if self.stored.len() >= BEEHIVE_MAX_OCCUPANTS {
78            return false;
79        }
80
81        self.stored.push(occupant);
82        true
83    }
84}
85
86/// Beehive and bee nest block entity.
87///
88/// Currently stores and persists occupants for worldgen bee nests. Full vanilla
89/// occupant ticking/release is blocked on bee entity support.
90pub struct BeehiveBlockEntity {
91    base: BlockEntityBase,
92    state: SyncMutex<BeehiveState>,
93}
94
95// SAFETY: This key is owned by Steel and uniquely identifies `BeehiveBlockEntity`.
96unsafe impl DowncastType for BeehiveBlockEntity {
97    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/beehive");
98}
99
100impl BeehiveBlockEntity {
101    /// Creates a new beehive block entity.
102    #[must_use]
103    pub fn new(level: Weak<World>, pos: BlockPos, state: BlockStateId) -> Self {
104        Self {
105            base: BlockEntityBase::new(&vanilla_block_entity_types::BEEHIVE, level, pos, state),
106            state: SyncMutex::new(BeehiveState { stored: Vec::new() }),
107        }
108    }
109
110    /// Stores a vanilla worldgen bee occupant.
111    ///
112    /// Mirrors `BeehiveBlockEntity.Occupant.create(ticksInHive)`.
113    pub fn store_worldgen_bee(&self, ticks_in_hive: i32) {
114        let stored = {
115            self.state
116                .lock()
117                .push_occupant(BeeOccupant::worldgen(ticks_in_hive))
118        };
119        if stored {
120            BlockEntity::set_changed(self);
121        }
122    }
123
124    /// Returns the number of stored occupants.
125    #[must_use]
126    pub fn occupant_count(&self) -> usize {
127        self.state.lock().stored.len()
128    }
129
130    /// Returns whether the hive currently stores no occupants.
131    #[must_use]
132    pub fn is_empty(&self) -> bool {
133        self.state.lock().stored.is_empty()
134    }
135}
136
137impl BlockEntity for BeehiveBlockEntity {
138    fn base(&self) -> &BlockEntityBase {
139        &self.base
140    }
141
142    fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>) {
143        let nbt: NbtCompoundView<'_, '_> = nbt.into();
144        let mut stored = Vec::new();
145
146        if let Some(bees) = nbt.list("bees")
147            && let Some(compounds) = bees.compounds()
148        {
149            for compound in compounds {
150                if stored.len() >= BEEHIVE_MAX_OCCUPANTS {
151                    break;
152                }
153                stored.push(BeeOccupant::load(compound));
154            }
155        }
156
157        self.state.lock().stored = stored;
158    }
159
160    fn save_additional(&self, nbt: &mut NbtCompound) {
161        let bees = self
162            .state
163            .lock()
164            .stored
165            .iter()
166            .map(BeeOccupant::save)
167            .collect::<Vec<_>>();
168        nbt.insert("bees", NbtList::Compound(bees));
169    }
170}