steel_core/entity/entities/objects/vehicles/
minecart.rs1use std::sync::{Arc, Weak};
4
5use glam::DVec3;
6use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
7use simdnbt::owned::NbtCompound;
8
9use steel_macros::entity_behavior;
10use steel_registry::entity_type::EntityTypeRef;
11use steel_registry::vanilla_entity_data::MinecartEntityData;
12use steel_registry::{items::ItemRef, vanilla_items};
13use steel_utils::axis::Axis;
14use steel_utils::block_util::FoundRectangle;
15use steel_utils::locks::SyncMutex;
16use steel_utils::types::InteractionHand;
17use steel_utils::{BlockPos, BlockStateId, Direction, DowncastType, DowncastTypeKey};
18
19use super::abstract_minecart::{
20 AbstractMinecart, AbstractMinecartBase, minecart_relative_portal_position,
21};
22use super::minecart_behavior::MinecartBehavior;
23use super::vehicle_entity::{
24 VEHICLE_DIMENSION_CHANGING_DELAY, VEHICLE_HURT_DURATION_TICKS, VehicleEntity,
25 vehicle_ignores_explosion,
26};
27use crate::behavior::InteractionResult;
28use crate::entity::{
29 DamageSource, Entity, EntityBase, EntityBaseLoad, EntityEventSource, EntityMovementEmission,
30 EntitySyncedData,
31};
32use crate::physics::{MoveResult, MoverType};
33use crate::player::Player;
34use crate::world::{Explosion, World};
35
36const ACTIVATOR_RAIL_DAMAGE: f32 = 50.0;
37
38#[entity_behavior(class = "Minecart")]
40pub struct MinecartEntity {
41 base: EntityBase,
42 entity_type: EntityTypeRef,
43 entity_data: SyncMutex<MinecartEntityData>,
44 minecart_base: AbstractMinecartBase,
45}
46
47unsafe impl DowncastType for MinecartEntity {
49 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/minecart");
50}
51
52impl MinecartEntity {
53 #[must_use]
55 pub fn new(entity_type: EntityTypeRef, id: i32, position: DVec3, world: Weak<World>) -> Self {
56 Self {
57 base: EntityBase::new(id, position, entity_type.dimensions, world),
58 entity_type,
59 entity_data: SyncMutex::new(MinecartEntityData::new()),
60 minecart_base: AbstractMinecartBase::new(),
61 }
62 }
63
64 #[must_use]
66 pub fn from_saved(entity_type: EntityTypeRef, load: EntityBaseLoad) -> Self {
67 Self {
68 base: EntityBase::from_load(load, entity_type.dimensions),
69 entity_type,
70 entity_data: SyncMutex::new(MinecartEntityData::new()),
71 minecart_base: AbstractMinecartBase::new(),
72 }
73 }
74}
75
76impl VehicleEntity for MinecartEntity {
77 fn hurt_time(&self) -> i32 {
78 *self.entity_data.lock().vehicle_entity().id_hurt.get()
79 }
80
81 fn set_hurt_time(&self, value: i32) {
82 self.entity_data
83 .lock()
84 .vehicle_entity_mut()
85 .id_hurt
86 .set(value);
87 }
88
89 fn hurt_dir(&self) -> i32 {
90 *self.entity_data.lock().vehicle_entity().id_hurtdir.get()
91 }
92
93 fn set_hurt_dir(&self, value: i32) {
94 self.entity_data
95 .lock()
96 .vehicle_entity_mut()
97 .id_hurtdir
98 .set(value);
99 }
100
101 fn damage(&self) -> f32 {
102 *self.entity_data.lock().vehicle_entity().id_damage.get()
103 }
104
105 fn set_damage(&self, value: f32) {
106 self.entity_data
107 .lock()
108 .vehicle_entity_mut()
109 .id_damage
110 .set(value);
111 }
112
113 fn vehicle_drop_item(&self) -> ItemRef {
114 &vanilla_items::MINECART
115 }
116}
117
118impl AbstractMinecart for MinecartEntity {
119 fn minecart_base(&self) -> &AbstractMinecartBase {
120 &self.minecart_base
121 }
122
123 fn is_rideable(&self) -> bool {
124 true
125 }
126
127 fn custom_display_block(&self) -> Option<BlockStateId> {
128 *self
129 .entity_data
130 .lock()
131 .abstract_minecart()
132 .id_custom_display_block
133 .get()
134 }
135
136 fn set_custom_display_block(&self, value: Option<BlockStateId>) {
137 self.entity_data
138 .lock()
139 .abstract_minecart_mut()
140 .id_custom_display_block
141 .set(value);
142 }
143
144 fn display_offset(&self) -> i32 {
145 *self
146 .entity_data
147 .lock()
148 .abstract_minecart()
149 .id_display_offset
150 .get()
151 }
152
153 fn set_display_offset(&self, value: i32) {
154 self.entity_data
155 .lock()
156 .abstract_minecart_mut()
157 .id_display_offset
158 .set(value);
159 }
160
161 fn activate_minecart(&self, _world: &World, _pos: BlockPos, powered: bool) {
162 if !powered {
163 return;
164 }
165 if self.is_vehicle() {
166 self.eject_passengers();
167 }
168 if self.hurt_time() == 0 {
169 self.set_hurt_dir(-self.hurt_dir());
170 self.set_hurt_time(VEHICLE_HURT_DURATION_TICKS);
171 self.set_damage(ACTIVATOR_RAIL_DAMAGE);
172 self.mark_hurt();
173 }
174 }
175}
176
177impl Entity for MinecartEntity {
178 fn interact(
179 &self,
180 player: &Player,
181 _hand: InteractionHand,
182 _location: DVec3,
183 ) -> InteractionResult {
184 if player.is_secondary_use_active() || self.is_vehicle() {
185 return InteractionResult::Pass;
186 }
187
188 let Some(world) = self.level() else {
189 return InteractionResult::Pass;
190 };
191 let Some(minecart) = world.get_entity_by_id(self.id()) else {
192 return InteractionResult::Pass;
193 };
194
195 if player.start_riding(&minecart) {
196 InteractionResult::SuccessServer
197 } else {
198 InteractionResult::Consume
199 }
200 }
201
202 fn base(&self) -> &EntityBase {
203 &self.base
204 }
205
206 fn entity_type(&self) -> EntityTypeRef {
207 self.entity_type
208 }
209
210 fn tick(self: Arc<Self>) {
211 let entity = self.into_shared_entity();
212 if let Some(minecart) = entity.as_abstract_minecart() {
213 minecart.tick_minecart(&entity);
214 }
215 }
216
217 fn get_default_gravity(&self) -> f64 {
218 self.minecart_default_gravity()
219 }
220
221 fn get_air_drag(&self) -> f64 {
222 self.minecart_air_drag()
223 }
224
225 fn block_speed_factor(&self) -> f32 {
226 self.minecart_block_speed_factor()
227 }
228
229 fn move_entity(self: Arc<Self>, mover_type: MoverType, delta: DVec3) -> Option<MoveResult> {
230 self.minecart_move_entity(mover_type, delta)
231 }
232
233 fn apply_effects_from_blocks(&self) {
234 self.minecart_apply_effects_from_blocks();
235 }
236
237 fn known_movement(&self) -> DVec3 {
238 MinecartBehavior::known_movement(self.default_known_movement())
239 }
240
241 fn motion_direction(&self) -> Direction {
242 MinecartBehavior::motion_direction(self)
243 }
244
245 fn is_on_rails(&self) -> bool {
246 self.minecart_base().on_rails()
247 }
248
249 fn is_pickable(&self) -> bool {
250 self.minecart_is_pickable()
251 }
252
253 fn is_pushable(&self) -> bool {
254 self.minecart_is_pushable()
255 }
256
257 fn movement_emission(&self) -> EntityMovementEmission {
258 self.minecart_movement_emission()
259 }
260
261 fn blocks_building(&self) -> bool {
262 self.minecart_blocks_building()
263 }
264
265 fn dimension_changing_delay(&self) -> i32 {
266 VEHICLE_DIMENSION_CHANGING_DELAY
267 }
268
269 fn get_relative_portal_position(&self, axis: Axis, area: FoundRectangle) -> DVec3 {
270 minecart_relative_portal_position(self, axis, area)
271 }
272
273 fn passenger_attachment_point(&self, passenger: &dyn Entity) -> DVec3 {
274 self.minecart_passenger_attachment_point(passenger)
275 }
276
277 fn can_collide_with(&self, other: &dyn Entity) -> bool {
278 self.minecart_can_collide_with(other)
279 }
280
281 fn push_entity(&self, entity: &dyn Entity) {
282 self.minecart_push_entity(entity);
283 }
284
285 fn hurt(&self, world: &World, source: &DamageSource, amount: f32) -> bool {
286 self.vehicle_hurt(world, source, amount)
287 }
288
289 fn ignore_explosion(&self, explosion: &dyn Explosion) -> bool {
290 vehicle_ignores_explosion(explosion)
291 }
292
293 fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
294 Some(&self.entity_data)
295 }
296
297 fn save_additional(&self, nbt: &mut NbtCompound) {
298 self.save_abstract_minecart_data(nbt);
299 }
300
301 fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
302 self.load_abstract_minecart_data(nbt);
303 }
304
305 }
307
308#[cfg(test)]
309#[path = "tests/minecart.rs"]
310mod tests;