use crossbeam::atomic::AtomicCell; use std::sync::atomic::{AtomicI32, Ordering}; use crate::entity::Entity; use pumpkin_protocol::java::client::play::Metadata; use crate::entity::EntityBase; use crate::world::loot::{LootContextParameters, LootTableExt}; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_util::GameMode; pub struct VehicleEntity { pub entity: Entity, pub hurt_time: AtomicI32, pub hurt_dir: AtomicI32, pub damage: AtomicCell, } impl VehicleEntity { pub const fn new(entity: Entity) -> Self { Self { entity, hurt_time: AtomicI32::new(0), hurt_dir: AtomicI32::new(1), damage: AtomicCell::new(0.0), } } pub fn tick(&self) { let current_hurt = self.hurt_time.load(Ordering::Relaxed); if current_hurt > 0 { self.hurt_time.store(current_hurt - 1, Ordering::Relaxed); } let current_damage = self.damage.load(); if current_damage > 0.0 { self.damage.store(current_damage - 1.0); } let mut update_event = crate::plugin::api::events::vehicle::vehicle_update::VehicleUpdateEvent::new( self.entity.entity_id, ); if let Some(server) = self.entity.world.load().server.upgrade() { server .plugin_manager .fire_blocking(&server, &mut update_event); } } pub async fn create(&self) { let mut create_event = crate::plugin::api::events::vehicle::vehicle_create::VehicleCreateEvent::new( self.entity.entity_id, ); if let Some(server) = self.entity.world.load().server.upgrade() { server.plugin_manager.fire(&server, &mut create_event).await; } } pub async fn move_vehicle( &self, from: pumpkin_util::math::vector3::Vector3, to: pumpkin_util::math::vector3::Vector3, ) { let mut move_event = crate::plugin::api::events::vehicle::vehicle_move::VehicleMoveEvent::new( self.entity.entity_id, from, to, ); if let Some(server) = self.entity.world.load().server.upgrade() { server.plugin_manager.fire(&server, &mut move_event).await; } } pub async fn collide_entity(&self, collided_entity_id: i32) { let mut base_event = crate::plugin::api::events::vehicle::vehicle_collision::VehicleCollisionEvent::new( self.entity.entity_id, ); let mut collide_event = crate::plugin::api::events::vehicle::vehicle_entity_collision::VehicleEntityCollisionEvent::new( self.entity.entity_id, collided_entity_id, ); if let Some(server) = self.entity.world.load().server.upgrade() { server.plugin_manager.fire(&server, &mut base_event).await; server .plugin_manager .fire(&server, &mut collide_event) .await; } } pub async fn collide_block(&self, block_pos: pumpkin_util::math::position::BlockPos) { let mut base_event = crate::plugin::api::events::vehicle::vehicle_collision::VehicleCollisionEvent::new( self.entity.entity_id, ); let mut collide_event = crate::plugin::api::events::vehicle::vehicle_block_collision::VehicleBlockCollisionEvent::new( self.entity.entity_id, block_pos, ); if let Some(server) = self.entity.world.load().server.upgrade() { server.plugin_manager.fire(&server, &mut base_event).await; server .plugin_manager .fire(&server, &mut collide_event) .await; } } pub fn set_damage(&self, damage: f32) { self.damage.store(damage); } pub fn get_damage(&self) -> f32 { self.damage.load() } pub fn set_hurt_time(&self, hurt_time: i32) { self.hurt_time.store(hurt_time, Ordering::Relaxed); } pub fn get_hurt_time(&self) -> i32 { self.hurt_time.load(Ordering::Relaxed) } pub fn set_hurt_dir(&self, hurt_dir: i32) { self.hurt_dir.store(hurt_dir, Ordering::Relaxed); } pub fn get_hurt_dir(&self) -> i32 { self.hurt_dir.load(Ordering::Relaxed) } pub fn send_wobble_metadata(&self) { self.entity.send_meta_data( &[ Metadata::new( pumpkin_data::tracked_data::boat::ID_HURT, VarInt(self.get_hurt_time()), ), Metadata::new( pumpkin_data::tracked_data::boat::ID_HURTDIR, VarInt(self.get_hurt_dir()), ), ], None, ); self.entity.send_meta_data( &[Metadata::new( pumpkin_data::tracked_data::boat::ID_DAMAGE, self.get_damage(), )], None, ); } pub fn kill_and_drop_self(&self) { let world = self.entity.world.load(); let entity_drops = world.level_info.load().game_rules.entity_drops; if entity_drops && let Some(loot_table) = &self.entity.entity_type.loot_table { let pos = self.entity.block_pos.load(); let is_raining = world.is_raining(); let is_thundering = world.is_thundering(); let params = LootContextParameters { is_raining: Some(is_raining), is_thundering: Some(is_thundering), world_time: world.level_info.load().day_time as u64, ..Default::default() }; for stack in loot_table.get_loot(params) { world.drop_stack(&pos, stack); } } self.entity.remove(); } pub fn damage_with_context(&self, amount: f32, source: Option<&dyn EntityBase>) -> bool { if !self.entity.is_alive() { return true; } let attacker_id = source.map(|s| s.get_entity().entity_id); let mut damage_event = crate::plugin::api::events::vehicle::vehicle_damage::VehicleDamageEvent::new( self.entity.entity_id, amount, attacker_id, ); if let Some(server) = self.entity.world.load().server.upgrade() { server .plugin_manager .fire_blocking(&server, &mut damage_event); } if damage_event.cancelled { return false; } let new_strength = self.apply_damage_wobble(amount); let is_creative = source .and_then(|s| s.get_player()) .is_some_and(|p| p.gamemode.load() == GameMode::Creative); if is_creative || new_strength > 40.0 { let mut destroy_event = crate::plugin::api::events::vehicle::vehicle_destroy::VehicleDestroyEvent::new( self.entity.entity_id, attacker_id, ); if let Some(server) = self.entity.world.load().server.upgrade() { server .plugin_manager .fire_blocking(&server, &mut destroy_event); } if destroy_event.cancelled { return false; } if is_creative { self.entity.remove(); } else { self.kill_and_drop_self(); } } true } /// Applies the standard minecart damage wobble without destroying the vehicle. /// TNT minecarts use this before deciding whether damage primes or breaks them. pub fn apply_damage_wobble(&self, amount: f32) -> f32 { let current_side = self.get_hurt_dir(); self.set_hurt_dir(-current_side); self.set_hurt_time(10); self.entity.velocity_dirty.store(true, Ordering::SeqCst); let current_strength = self.get_damage(); let new_strength = current_strength + amount * 10.0; self.set_damage(new_strength); self.send_wobble_metadata(); new_strength } } #[cfg(test)] mod tests { use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::Metadata; use pumpkin_util::version::JavaMinecraftVersion; fn wobble_integer_metadata(version: JavaMinecraftVersion) -> Vec { let mut bytes = Vec::new(); for metadata in [ Metadata::new(pumpkin_data::tracked_data::boat::ID_HURT, VarInt(10)), Metadata::new(pumpkin_data::tracked_data::boat::ID_HURTDIR, VarInt(-1)), ] { metadata.write(&mut bytes, &version).unwrap(); } bytes } #[test] fn wobble_integers_serialize_for_legacy_and_current_clients() { let expected = vec![8, 1, 10, 9, 1, 0xff, 0xff, 0xff, 0xff, 0x0f]; assert_eq!( wobble_integer_metadata(JavaMinecraftVersion::V_1_21_11), expected ); assert_eq!( wobble_integer_metadata(JavaMinecraftVersion::V_26_2), expected ); } }