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