Skip to main content

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

1//! Shared vanilla `VehicleEntity` damage handling.
2
3use crate::entity::{DamageSource, Entity, RemovalReason};
4use crate::world::{Explosion, World};
5use steel_registry::data_components::vanilla_components::CUSTOM_NAME;
6use steel_registry::item_stack::ItemStack;
7use steel_registry::items::ItemRef;
8use steel_registry::vanilla_game_events;
9use steel_registry::vanilla_game_rules::{ENTITY_DROPS, MOB_GRIEFING};
10
11const VEHICLE_DAMAGE_MULTIPLIER: f32 = 10.0;
12const VEHICLE_DESTROY_DAMAGE_THRESHOLD: f32 = 40.0;
13pub(super) const VEHICLE_HURT_DURATION_TICKS: i32 = 10;
14
15/// Implemented by vehicle entities (minecarts, boats) for vanilla
16/// `VehicleEntity` damage handling.
17pub trait VehicleEntity: Entity {
18    /// Returns the vehicle's remaining hurt time.
19    fn hurt_time(&self) -> i32;
20
21    /// Sets the vehicle's remaining hurt time.
22    fn set_hurt_time(&self, value: i32);
23
24    /// Returns the vehicle's hurt direction animation.
25    fn hurt_dir(&self) -> i32;
26
27    /// Sets the vehicle's hurt direction animation.
28    fn set_hurt_dir(&self, value: i32);
29
30    /// Returns the accumulated vehicle damage.
31    fn damage(&self) -> f32;
32
33    /// Sets the accumulated vehicle damage.
34    fn set_damage(&self, value: f32);
35
36    /// Returns the item dropped when this vehicle is destroyed.
37    fn vehicle_drop_item(&self) -> ItemRef;
38
39    /// Returns whether the `source` should destroy this vehicle.
40    fn should_source_destroy(&self, _source: &DamageSource) -> bool {
41        false
42    }
43
44    #[expect(
45        clippy::neg_cmp_op_on_partial_ord,
46        reason = "negated comparisons preserve Vanilla's NaN branch behavior"
47    )]
48    /// Applies vehicle damage, updates the hurt animation, and handles destruction.
49    fn vehicle_hurt(&self, world: &World, source: &DamageSource, damage: f32) -> bool {
50        if self.is_removed() {
51            return true;
52        }
53
54        if self.is_invulnerable_to_base(source) {
55            return false;
56        }
57        self.set_hurt_dir(-self.hurt_dir());
58        self.set_hurt_time(VEHICLE_HURT_DURATION_TICKS);
59        self.mark_hurt();
60        let new_damage = self.damage() + damage * VEHICLE_DAMAGE_MULTIPLIER;
61        self.set_damage(new_damage);
62
63        let source_entity = source.causing_entity();
64        self.game_event_with_source_entity(
65            &vanilla_game_events::ENTITY_DAMAGE,
66            source_entity.map(AsRef::as_ref),
67        );
68
69        let creative_player = source_entity
70            .and_then(|entity| entity.as_player())
71            .is_some_and(|player| player.abilities.lock().instabuild);
72
73        if (creative_player || !(new_damage > VEHICLE_DESTROY_DAMAGE_THRESHOLD))
74            && !self.should_source_destroy(source)
75        {
76            if creative_player {
77                self.set_removed(RemovalReason::Discarded);
78            }
79        } else {
80            self.vehicle_destroy(world, source);
81        }
82        true
83    }
84
85    /// Destroys the vehicle using its default drop item.
86    fn vehicle_destroy(&self, world: &World, _source: &DamageSource) {
87        self.vehicle_destroy_with_item(world, self.vehicle_drop_item());
88    }
89
90    /// Destroys the vehicle and drops the given item, when entity drops are enabled.
91    fn vehicle_destroy_with_item(&self, world: &World, drop_item: ItemRef) {
92        self.kill(world);
93        if world.get_game_rule(&ENTITY_DROPS) {
94            let mut item = ItemStack::new(drop_item);
95            if let Some(name) = self.custom_name() {
96                item.set(CUSTOM_NAME, name);
97            }
98            self.spawn_at_location(item, 0.0);
99        }
100    }
101}
102
103/// Vanilla `VehicleEntity.getDimensionChangingDelay`.
104pub(crate) const VEHICLE_DIMENSION_CHANGING_DELAY: i32 = 10;
105
106/// Vanilla `VehicleEntity.ignoreExplosion`: mob explosions leave vehicles intact while
107/// `mobGriefing` is off.
108pub(crate) fn vehicle_ignores_explosion(explosion: &dyn Explosion) -> bool {
109    explosion
110        .indirect_source_entity()
111        .is_some_and(|entity| entity.as_mob().is_some())
112        && !explosion.world().get_game_rule(&MOB_GRIEFING)
113}
114
115#[cfg(test)]
116#[path = "tests/vehicle_entity.rs"]
117mod tests;