use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use crossbeam::atomic::AtomicCell; use crate::entity::player::Player; use crate::entity::{Entity, EntityBase, living::LivingEntity}; use crate::server::Server; use pumpkin_data::damage::DamageType; use pumpkin_data::item_stack::ItemStack; use pumpkin_protocol::java::client::play::Metadata; use pumpkin_util::math::vector3::Vector3; use crate::entity::vehicle::vehicle::VehicleEntity; pub struct BoatEntity { pub vehicle: VehicleEntity, ticks_underwater: AtomicCell, left_paddle_moving: AtomicBool, right_paddle_moving: AtomicBool, } impl BoatEntity { pub const fn new(entity: Entity) -> Self { Self { vehicle: VehicleEntity::new(entity), ticks_underwater: AtomicCell::new(0.0), left_paddle_moving: AtomicBool::new(false), right_paddle_moving: AtomicBool::new(false), } } pub fn set_paddles(&self, left: bool, right: bool) { self.left_paddle_moving.store(left, Ordering::Relaxed); self.right_paddle_moving.store(right, Ordering::Relaxed); self.vehicle.entity.send_meta_data( &[ Metadata::new(pumpkin_data::tracked_data::boat::ID_PADDLE_LEFT, left), Metadata::new(pumpkin_data::tracked_data::boat::ID_PADDLE_RIGHT, right), ], None, ); } fn send_wobble_metadata(&self) { self.vehicle.send_wobble_metadata(); } } impl EntityBase for BoatEntity { fn get_entity(&self) -> &Entity { &self.vehicle.entity } fn get_living_entity(&self) -> Option<&LivingEntity> { None } fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { self.vehicle.tick(); let underwater = self.ticks_underwater.load(); if self.vehicle.entity.touching_water.load(Ordering::Relaxed) { self.ticks_underwater.store((underwater + 1.0).min(60.0)); } else if underwater > 0.0 { self.ticks_underwater.store((underwater - 1.0).max(0.0)); } } fn init_data_tracker(&self) { self.send_wobble_metadata(); } fn can_hit(&self) -> bool { self.vehicle.entity.is_alive() } fn is_collidable(&self, _entity: Option>) -> bool { true } fn damage_with_context( &self, _caller: &dyn EntityBase, amount: f32, _damage_type: DamageType, _position: Option>, source: Option<&dyn EntityBase>, _cause: Option<&dyn EntityBase>, ) -> bool { self.vehicle.damage_with_context(amount, source) } fn interact(&self, player: &Arc, _item_stack: &mut ItemStack) -> bool { if player.get_entity().is_sneaking() { return false; } if self.ticks_underwater.load() >= 60.0 { return false; } if self .vehicle .entity .passengers .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .len() >= 2 { return false; } if player.get_entity().has_vehicle() { return false; } let world = self.vehicle.entity.world.load(); let Some(vehicle) = world.get_entity_by_id(self.vehicle.entity.entity_id) else { return false; }; let Some(passenger) = world.get_player_by_id(player.entity_id()) else { return false; }; self.vehicle .entity .add_passenger(vehicle, passenger as Arc); true } fn set_paddle_state(&self, left: bool, right: bool) { self.set_paddles(left, right); } fn cast_any(&self) -> &dyn std::any::Any { self } fn is_pushable(&self) -> bool { true } }