steel_core/entity/entities/objects/vehicles/
vehicle_entity.rs1use 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
15pub trait VehicleEntity: Entity {
18 fn hurt_time(&self) -> i32;
20
21 fn set_hurt_time(&self, value: i32);
23
24 fn hurt_dir(&self) -> i32;
26
27 fn set_hurt_dir(&self, value: i32);
29
30 fn damage(&self) -> f32;
32
33 fn set_damage(&self, value: f32);
35
36 fn vehicle_drop_item(&self) -> ItemRef;
38
39 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 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 fn vehicle_destroy(&self, world: &World, _source: &DamageSource) {
87 self.vehicle_destroy_with_item(world, self.vehicle_drop_item());
88 }
89
90 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
103pub(crate) const VEHICLE_DIMENSION_CHANGING_DELAY: i32 = 10;
105
106pub(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;