steel_core/entity/
block_attached_entity.rs1use crate::entity::damage::DamageSource;
8use crate::entity::mob::block_pos_distance_sqr;
9use crate::entity::{BorrowedNbtCompoundView, EntityMoveError, SharedEntity};
10use crate::entity::{Entity, RemovalReason};
11use crate::physics::{MoveResult, MoverType};
12use crate::world::World;
13use glam::DVec3;
14use simdnbt::owned::{NbtCompound, NbtTag};
15use std::sync::Arc;
16use steel_registry::vanilla_damage_types;
17use steel_registry::vanilla_game_events;
18use steel_registry::vanilla_game_rules::MOB_GRIEFING;
19use steel_utils::BlockPos;
20use steel_utils::locks::SyncMutex;
21
22pub const VALID_READ_DISTANCE_SQR: f64 = 16.0 * 16.0;
23
24pub const CHECK_SURVIVAL_FREQUENCY_TICKS: u32 = 100;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30struct BlockAttachedEntityState {
31 check_interval: u32,
32 pos: BlockPos,
33}
34
35impl BlockAttachedEntityState {
36 pub const fn new(pos: BlockPos) -> Self {
37 Self {
38 check_interval: 0,
39 pos,
40 }
41 }
42}
43
44#[derive(Debug)]
46pub struct BlockAttachedEntityBase {
47 state: SyncMutex<BlockAttachedEntityState>,
48}
49
50impl BlockAttachedEntityBase {
51 pub const fn new(pos: BlockPos) -> Self {
53 BlockAttachedEntityBase {
54 state: SyncMutex::new(BlockAttachedEntityState::new(pos)),
55 }
56 }
57
58 pub fn pos(&self) -> BlockPos {
60 self.state.lock().pos
61 }
62
63 pub fn set_pos(&self, pos: BlockPos) {
65 self.state.lock().pos = pos;
66 }
67
68 pub fn should_check_survival(&self) -> bool {
72 let mut state = self.state.lock();
73
74 if state.check_interval == CHECK_SURVIVAL_FREQUENCY_TICKS {
75 state.check_interval = 0;
76 true
77 } else {
78 state.check_interval += 1;
79 false
80 }
81 }
82}
83
84pub trait BlockAttachedEntity: Entity {
85 fn block_attached_entity_base(&self) -> &BlockAttachedEntityBase;
87
88 fn survives(&self) -> bool;
89
90 fn drop_item(&self, caused_by: Option<&dyn Entity>);
91
92 fn recalculate_bounding_box(&self) -> Result<(), EntityMoveError>;
93
94 fn tick_block_attached_entity(self: Arc<Self>) {
95 self.check_below_world();
96 if self.block_attached_entity_base().should_check_survival() {
97 if !self.is_removed() && !self.survives() {
99 self.set_removed(RemovalReason::Discarded);
100 self.drop_item(None);
101 }
102 }
103 }
104
105 fn is_pickable_block_attached_entity(&self) -> bool {
106 true
107 }
108
109 fn skip_attack_interaction_block_attached_entity(&self, source: &SharedEntity) -> bool {
110 if let Some(player) = source.as_player()
111 && let Some(world) = source.level()
112 {
113 let damage_source = DamageSource::environment(&vanilla_damage_types::PLAYER_ATTACK)
114 .with_causing_entity(Arc::clone(source))
115 .with_direct_entity(Arc::clone(source));
116
117 !world.may_interact(player, self.block_attached_entity_base().pos())
118 || self.hurt(&world, &damage_source, 0.0)
119 } else {
120 false
121 }
122 }
123
124 fn kill_attributed(&self, attributed_to: Option<&dyn Entity>) {
127 self.set_removed(RemovalReason::Killed);
128 self.game_event_with_source_entity(
129 &vanilla_game_events::ENTITY_DIE,
130 attributed_to.or(Some(self.as_entity_event_source())),
131 );
132 }
133
134 fn hurt_block_attached_entity(
135 &self,
136 world: &World,
137 source: &DamageSource,
138 _amount: f32,
139 ) -> bool {
140 if self.is_invulnerable_to_base(source) {
141 return false;
142 }
143
144 let causing_entity = source.causing_entity();
145
146 if !world.get_game_rule(&MOB_GRIEFING)
147 && let Some(causing_entity) = &causing_entity
148 && causing_entity.is_mob()
149 {
150 return false;
151 }
152
153 if !self.is_removed() {
154 self.kill_attributed(causing_entity.map(AsRef::as_ref));
155 self.mark_hurt();
156 self.drop_item(causing_entity.map(AsRef::as_ref));
157 }
158
159 true
160 }
161
162 fn drop_if_dvec3_is_nonzero(&self, impulse: DVec3) {
163 if let Some(world) = self.level()
164 && !self.is_removed()
165 && impulse.length_squared() > 0.0
166 {
167 self.kill(&world);
168 self.drop_item(None);
169 }
170 }
171
172 fn move_entity_block_attached_entity(
173 self: Arc<Self>,
174 _mover_type: MoverType,
175 delta: DVec3,
176 ) -> Option<MoveResult> {
177 self.drop_if_dvec3_is_nonzero(delta);
178 None
179 }
180
181 fn push_impulse_block_attached_entity(&self, impulse: DVec3) {
182 self.drop_if_dvec3_is_nonzero(impulse);
183 }
184
185 fn save_block_attached_entity(&self, nbt: &mut NbtCompound) {
186 let block_pos = self.block_attached_entity_base().pos();
187 nbt.insert(
188 "block_pos",
189 NbtTag::IntArray(vec![block_pos.x(), block_pos.y(), block_pos.z()]),
190 );
191 }
192
193 fn load_block_attached_entity(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
194 if let Some(block_pos_vec) = nbt.int_array("block_pos")
195 && block_pos_vec.len() == 3
196 {
197 let block_pos = BlockPos::new(block_pos_vec[0], block_pos_vec[1], block_pos_vec[2]);
198 if block_pos_distance_sqr(block_pos, self.block_position()) >= VALID_READ_DISTANCE_SQR {
199 log::warn!("Block-attached entity at invalid position: {block_pos:?}");
200 return;
201 }
202 self.block_attached_entity_base().set_pos(block_pos);
203 }
204 }
205
206 fn refresh_dimensions_block_attached_entity(&self) {}
207
208 fn try_set_position_block_attached_entity(&self, pos: DVec3) -> Result<(), EntityMoveError> {
209 self.block_attached_entity_base()
210 .set_pos(BlockPos::containing(pos.x, pos.y, pos.z));
211 self.recalculate_bounding_box()?;
212 self.base().mark_velocity_sync();
213 Ok(())
214 }
215
216 }