steel_core/entity/entities/
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 pub fn snap_to(&self, position: DVec3, yaw: f32, pitch: f32) {
84 if let Err(error) = self.base.try_set_position(position) {
85 panic!(
86 "failed to commit end crystal {} snap position: {error}",
87 self.base.id()
88 );
89 }
90 self.base.set_rotation((yaw, pitch));
91 self.set_old_position_to_current();
92 }
93
94 const fn nbt_bool(value: bool) -> i8 {
95 if value { 1 } else { 0 }
96 }
97}
98
99impl Entity for EndCrystalEntity {
100 fn base(&self) -> &EntityBase {
101 &self.base
102 }
103
104 fn entity_type(&self) -> EntityTypeRef {
105 self.entity_type
106 }
107
108 fn tick(&self) {
109 }
111
112 fn is_pickable(&self) -> bool {
113 true
114 }
115
116 fn blocks_building(&self) -> bool {
117 true
118 }
119
120 fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
121 Some(&self.entity_data)
122 }
123
124 fn save_additional(&self, nbt: &mut NbtCompound) {
125 if let Some(target) = self.beam_target() {
126 nbt.insert(
127 "beam_target",
128 NbtTag::IntArray(vec![target.x(), target.y(), target.z()]),
129 );
130 }
131
132 nbt.insert("ShowBottom", Self::nbt_bool(self.shows_bottom()));
133 }
134
135 fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
136 if let Some(target) = nbt.int_array("beam_target")
137 && target.len() == 3
138 {
139 self.set_beam_target(Some(BlockPos::new(target[0], target[1], target[2])));
140 }
141
142 if let Some(show_bottom) = nbt.byte("ShowBottom") {
143 self.set_show_bottom(show_bottom != 0);
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 use steel_registry::vanilla_entities;
153
154 #[test]
155 fn end_crystal_does_not_duplicate_shared_invulnerable_state() {
156 let crystal = EndCrystalEntity::new(
157 &vanilla_entities::END_CRYSTAL,
158 1,
159 DVec3::new(1.5, 2.5, 3.5),
160 Weak::new(),
161 );
162 crystal.set_invulnerable(true);
163
164 let mut nbt = NbtCompound::new();
165 crystal.save_additional(&mut nbt);
166
167 assert_eq!(nbt.byte("Invulnerable"), None);
168 }
169
170 #[test]
171 fn end_crystal_is_pickable_like_vanilla() {
172 let crystal = EndCrystalEntity::new(
173 &vanilla_entities::END_CRYSTAL,
174 1,
175 DVec3::new(1.5, 2.5, 3.5),
176 Weak::new(),
177 );
178
179 assert!(crystal.is_pickable());
180 }
181
182 #[test]
183 fn end_crystal_blocks_building_like_vanilla() {
184 let crystal =
185 EndCrystalEntity::new(&vanilla_entities::END_CRYSTAL, 1, DVec3::ZERO, Weak::new());
186
187 assert!(crystal.blocks_building());
188 }
189}