steel_core/entity/entities/objects/explosives/
end_crystal.rs1use std::sync::Weak;
4
5use glam::DVec3;
6use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
7use simdnbt::owned::{NbtCompound, NbtTag};
8use steel_macros::entity_behavior;
9use steel_registry::entity_type::EntityTypeRef;
10use steel_registry::vanilla_entity_data::EndCrystalEntityData;
11use steel_utils::{BlockPos, locks::SyncMutex};
12use steel_utils::{DowncastType, DowncastTypeKey};
13
14use crate::entity::{Entity, EntityBase, EntityBaseLoad, EntitySyncedData};
15use crate::world::World;
16
17#[entity_behavior(class = "EndCrystal")]
23pub struct EndCrystalEntity {
24 base: EntityBase,
25 entity_type: EntityTypeRef,
26 entity_data: SyncMutex<EndCrystalEntityData>,
27}
28
29unsafe impl DowncastType for EndCrystalEntity {
31 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/end_crystal");
32}
33
34impl EndCrystalEntity {
35 #[must_use]
37 pub fn new(entity_type: EntityTypeRef, id: i32, position: DVec3, world: Weak<World>) -> Self {
38 Self {
39 base: EntityBase::new(id, position, entity_type.dimensions, world),
40 entity_type,
41 entity_data: SyncMutex::new(EndCrystalEntityData::new()),
42 }
43 }
44
45 #[must_use]
47 pub fn from_saved(entity_type: EntityTypeRef, load: EntityBaseLoad) -> Self {
48 Self {
49 base: EntityBase::from_load(load, entity_type.dimensions),
50 entity_type,
51 entity_data: SyncMutex::new(EndCrystalEntityData::new()),
52 }
53 }
54
55 pub fn set_beam_target(&self, target: Option<BlockPos>) {
57 self.entity_data.lock().beam_target.set(target);
58 }
59
60 #[must_use]
62 pub fn beam_target(&self) -> Option<BlockPos> {
63 *self.entity_data.lock().beam_target.get()
64 }
65
66 pub fn set_show_bottom(&self, show_bottom: bool) {
68 self.entity_data.lock().show_bottom.set(show_bottom);
69 }
70
71 #[must_use]
73 pub fn shows_bottom(&self) -> bool {
74 *self.entity_data.lock().show_bottom.get()
75 }
76
77 const fn nbt_bool(value: bool) -> i8 {
78 if value { 1 } else { 0 }
79 }
80}
81
82impl Entity for EndCrystalEntity {
83 fn base(&self) -> &EntityBase {
84 &self.base
85 }
86
87 fn entity_type(&self) -> EntityTypeRef {
88 self.entity_type
89 }
90
91 fn tick(&self) {
92 }
94
95 fn is_pickable(&self) -> bool {
96 true
97 }
98
99 fn blocks_building(&self) -> bool {
100 true
101 }
102
103 fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
104 Some(&self.entity_data)
105 }
106
107 fn save_additional(&self, nbt: &mut NbtCompound) {
108 if let Some(target) = self.beam_target() {
109 nbt.insert(
110 "beam_target",
111 NbtTag::IntArray(vec![target.x(), target.y(), target.z()]),
112 );
113 }
114
115 nbt.insert("ShowBottom", Self::nbt_bool(self.shows_bottom()));
116 }
117
118 fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
119 if let Some(target) = nbt.int_array("beam_target")
120 && target.len() == 3
121 {
122 self.set_beam_target(Some(BlockPos::new(target[0], target[1], target[2])));
123 }
124
125 if let Some(show_bottom) = nbt.byte("ShowBottom") {
126 self.set_show_bottom(show_bottom != 0);
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 use steel_registry::vanilla_entities;
136
137 #[test]
138 fn end_crystal_does_not_duplicate_shared_invulnerable_state() {
139 let crystal = EndCrystalEntity::new(
140 &vanilla_entities::END_CRYSTAL,
141 1,
142 DVec3::new(1.5, 2.5, 3.5),
143 Weak::new(),
144 );
145 crystal.set_invulnerable(true);
146
147 let mut nbt = NbtCompound::new();
148 crystal.save_additional(&mut nbt);
149
150 assert_eq!(nbt.byte("Invulnerable"), None);
151 }
152
153 #[test]
154 fn end_crystal_is_pickable_like_vanilla() {
155 let crystal = EndCrystalEntity::new(
156 &vanilla_entities::END_CRYSTAL,
157 1,
158 DVec3::new(1.5, 2.5, 3.5),
159 Weak::new(),
160 );
161
162 assert!(crystal.is_pickable());
163 }
164
165 #[test]
166 fn end_crystal_blocks_building_like_vanilla() {
167 let crystal =
168 EndCrystalEntity::new(&vanilla_entities::END_CRYSTAL, 1, DVec3::ZERO, Weak::new());
169
170 assert!(crystal.blocks_building());
171 }
172}