Skip to main content

steel_core/entity/entities/objects/vehicles/
minecart.rs

1//! Vanilla rideable minecart.
2
3use 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/// A minecart entity.
39#[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
47// SAFETY: This key is owned by Steel and uniquely identifies `MinecartEntity`.
48unsafe impl DowncastType for MinecartEntity {
49    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/minecart");
50}
51
52impl MinecartEntity {
53    /// Creates a new minecart entity.
54    #[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    /// Creates a minecart entity from saved data.
65    #[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    // TODO: Implement getPickResult.
306}
307
308#[cfg(test)]
309#[path = "tests/minecart.rs"]
310mod tests;