feat: add minecarts
This commit is contained in:
parent
c52531b37a
commit
daa0111009
@ -28,7 +28,7 @@ struct Rail {
|
|||||||
impl Rail {
|
impl Rail {
|
||||||
fn find_with_elevation(world: &World, position: BlockPos) -> Option<Self> {
|
fn find_with_elevation(world: &World, position: BlockPos) -> Option<Self> {
|
||||||
let (block, block_state) = world.get_block_and_state_id(&position);
|
let (block, block_state) = world.get_block_and_state_id(&position);
|
||||||
if block.has_tag(&tag::Item::MINECRAFT_RAILS) {
|
if block.has_tag(&tag::Block::MINECRAFT_RAILS) {
|
||||||
let properties = RailProperties::new(block_state, block);
|
let properties = RailProperties::new(block_state, block);
|
||||||
return Some(Self {
|
return Some(Self {
|
||||||
block,
|
block,
|
||||||
@ -40,7 +40,7 @@ impl Rail {
|
|||||||
|
|
||||||
let pos = position.up();
|
let pos = position.up();
|
||||||
let (block, block_state) = world.get_block_and_state_id(&pos);
|
let (block, block_state) = world.get_block_and_state_id(&pos);
|
||||||
if block.has_tag(&tag::Item::MINECRAFT_RAILS) {
|
if block.has_tag(&tag::Block::MINECRAFT_RAILS) {
|
||||||
let properties = RailProperties::new(block_state, block);
|
let properties = RailProperties::new(block_state, block);
|
||||||
return Some(Self {
|
return Some(Self {
|
||||||
block,
|
block,
|
||||||
@ -52,7 +52,7 @@ impl Rail {
|
|||||||
|
|
||||||
let pos = position.down();
|
let pos = position.down();
|
||||||
let (block, block_state) = world.get_block_and_state_id(&pos);
|
let (block, block_state) = world.get_block_and_state_id(&pos);
|
||||||
if block.has_tag(&tag::Item::MINECRAFT_RAILS) {
|
if block.has_tag(&tag::Block::MINECRAFT_RAILS) {
|
||||||
let properties = RailProperties::new(block_state, block);
|
let properties = RailProperties::new(block_state, block);
|
||||||
return Some(Self {
|
return Some(Self {
|
||||||
block,
|
block,
|
||||||
|
|||||||
@ -2574,6 +2574,10 @@ impl EntityBase for LivingEntity {
|
|||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_pushable(&self) -> bool {
|
||||||
|
self.health.load() > 0.0 && !self.dead.load(Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
fn cast_any(&self) -> &dyn std::any::Any {
|
fn cast_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@ -312,6 +312,233 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
|
|||||||
Box::pin(async {})
|
Box::pin(async {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_passenger(&self) -> EntityBaseFuture<'_, bool> {
|
||||||
|
Box::pin(async move { self.get_entity().has_vehicle().await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_vehicle(&self) -> EntityBaseFuture<'_, bool> {
|
||||||
|
Box::pin(async move { self.get_entity().has_passengers().await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_passenger<'a>(&'a self, other: &'a Arc<dyn EntityBase>) -> EntityBaseFuture<'a, bool> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.get_entity()
|
||||||
|
.passengers
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.get_entity().entity_id == other.get_entity().entity_id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_entity<'a>(
|
||||||
|
&'a self,
|
||||||
|
caller: &'a Arc<dyn EntityBase>,
|
||||||
|
motion: Vector3<f64>,
|
||||||
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.get_entity().move_entity(caller, motion).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_pushable(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push<'a>(&'a self, entity: &'a Arc<dyn EntityBase>) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let self_entity = self.get_entity();
|
||||||
|
let other_entity = entity.get_entity();
|
||||||
|
|
||||||
|
if self_entity.no_clip.load(Ordering::Relaxed)
|
||||||
|
|| other_entity.no_clip.load(Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let passengers = self_entity.passengers.lock().await;
|
||||||
|
if passengers
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.get_entity().entity_id == other_entity.entity_id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let passengers = other_entity.passengers.lock().await;
|
||||||
|
if passengers
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.get_entity().entity_id == self_entity.entity_id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dx = other_entity.pos.load().x - self_entity.pos.load().x;
|
||||||
|
let mut dz = other_entity.pos.load().z - self_entity.pos.load().z;
|
||||||
|
let mut d = dx.abs().max(dz.abs());
|
||||||
|
if d >= 0.01 {
|
||||||
|
d = d.sqrt();
|
||||||
|
dx /= d;
|
||||||
|
dz /= d;
|
||||||
|
let mut d2 = 1.0 / d;
|
||||||
|
if d2 > 1.0 {
|
||||||
|
d2 = 1.0;
|
||||||
|
}
|
||||||
|
dx *= d2;
|
||||||
|
dz *= d2;
|
||||||
|
dx *= 0.05;
|
||||||
|
dz *= 0.05;
|
||||||
|
|
||||||
|
if !self_entity.has_passengers().await && self.is_pushable() {
|
||||||
|
let mut vel = self_entity.velocity.load();
|
||||||
|
vel.x -= dx;
|
||||||
|
vel.z -= dz;
|
||||||
|
self_entity.velocity.store(vel);
|
||||||
|
self_entity.send_velocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !other_entity.has_passengers().await && entity.is_pushable() {
|
||||||
|
let mut vel = other_entity.velocity.load();
|
||||||
|
vel.x += dx;
|
||||||
|
vel.z += dz;
|
||||||
|
other_entity.velocity.store(vel);
|
||||||
|
other_entity.send_velocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn push_entities<'a>(
|
||||||
|
&'a self,
|
||||||
|
dyn_self: &'a Arc<dyn EntityBase>,
|
||||||
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut picked_up = false;
|
||||||
|
let mut pushed = false;
|
||||||
|
let self_entity = self.get_entity();
|
||||||
|
let entity_bb = self_entity.bounding_box.load();
|
||||||
|
|
||||||
|
if !self.is_pushable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let world = self_entity.world.load();
|
||||||
|
|
||||||
|
let is_rideable_minecart = self_entity.entity_type.id == EntityType::MINECART.id;
|
||||||
|
let is_abstract_minecart = is_rideable_minecart
|
||||||
|
|| self_entity.entity_type.id == EntityType::CHEST_MINECART.id
|
||||||
|
|| self_entity.entity_type.id == EntityType::COMMAND_BLOCK_MINECART.id
|
||||||
|
|| self_entity.entity_type.id == EntityType::FURNACE_MINECART.id
|
||||||
|
|| self_entity.entity_type.id == EntityType::HOPPER_MINECART.id
|
||||||
|
|| self_entity.entity_type.id == EntityType::SPAWNER_MINECART.id
|
||||||
|
|| self_entity.entity_type.id == EntityType::TNT_MINECART.id;
|
||||||
|
|
||||||
|
let is_minecart_fn = |id| -> bool {
|
||||||
|
id == EntityType::MINECART.id
|
||||||
|
|| id == EntityType::CHEST_MINECART.id
|
||||||
|
|| id == EntityType::COMMAND_BLOCK_MINECART.id
|
||||||
|
|| id == EntityType::FURNACE_MINECART.id
|
||||||
|
|| id == EntityType::HOPPER_MINECART.id
|
||||||
|
|| id == EntityType::SPAWNER_MINECART.id
|
||||||
|
|| id == EntityType::TNT_MINECART.id
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_abstract_minecart {
|
||||||
|
let is_vehicle = self.is_vehicle().await;
|
||||||
|
|
||||||
|
if is_rideable_minecart && !is_vehicle {
|
||||||
|
let pickup_bb = entity_bb.expand(0.2, 0.0, 0.2);
|
||||||
|
let other_entities = world.get_entities_at_box(&pickup_bb);
|
||||||
|
|
||||||
|
for other in other_entities {
|
||||||
|
if other.get_entity().entity_id != self_entity.entity_id {
|
||||||
|
let other_type = other.get_entity().entity_type.id;
|
||||||
|
let is_iron_golem = other_type == EntityType::IRON_GOLEM.id;
|
||||||
|
let is_other_minecart = is_minecart_fn(other_type);
|
||||||
|
|
||||||
|
if !is_iron_golem
|
||||||
|
&& !is_other_minecart
|
||||||
|
&& !other.is_passenger().await
|
||||||
|
&& other.is_pushable()
|
||||||
|
&& other.get_entity().riding_cooldown.load(Relaxed) == 0
|
||||||
|
{
|
||||||
|
dyn_self
|
||||||
|
.get_entity()
|
||||||
|
.add_passenger(dyn_self.clone(), other.clone())
|
||||||
|
.await;
|
||||||
|
picked_up = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let push_bb = entity_bb.expand(1.0e-7, 1.0e-7, 1.0e-7);
|
||||||
|
|
||||||
|
let other_entities = world.get_entities_at_box(&push_bb);
|
||||||
|
for other in other_entities {
|
||||||
|
if other.get_entity().entity_id != self_entity.entity_id {
|
||||||
|
let other_type = other.get_entity().entity_type.id;
|
||||||
|
let is_other_minecart = is_minecart_fn(other_type);
|
||||||
|
let is_iron_golem = other_type == EntityType::IRON_GOLEM.id;
|
||||||
|
|
||||||
|
if is_rideable_minecart {
|
||||||
|
if (is_iron_golem
|
||||||
|
|| is_other_minecart
|
||||||
|
|| is_vehicle
|
||||||
|
|| !other.get_entity().has_vehicle().await)
|
||||||
|
&& other.is_pushable()
|
||||||
|
{
|
||||||
|
dyn_self.push(&other).await;
|
||||||
|
pushed = true;
|
||||||
|
}
|
||||||
|
} else if !self.has_passenger(&other).await
|
||||||
|
&& other.is_pushable()
|
||||||
|
&& is_other_minecart
|
||||||
|
{
|
||||||
|
dyn_self.push(&other).await;
|
||||||
|
pushed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let players = world.get_players_at_box(&push_bb);
|
||||||
|
for player in players {
|
||||||
|
if player.get_entity().entity_id != self_entity.entity_id
|
||||||
|
&& is_rideable_minecart
|
||||||
|
{
|
||||||
|
let player_base: Arc<dyn EntityBase> = player.clone();
|
||||||
|
dyn_self.push(&player_base).await;
|
||||||
|
pushed = true;
|
||||||
|
// Non-rideable minecarts (hoppers, chests) do not push players in vanilla.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let other_entities = world.get_entities_at_box(&entity_bb);
|
||||||
|
for other in other_entities {
|
||||||
|
if other.get_entity().entity_id != self_entity.entity_id {
|
||||||
|
dyn_self.push(&other).await;
|
||||||
|
pushed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let players = world.get_players_at_box(&entity_bb);
|
||||||
|
for player in players {
|
||||||
|
if player.get_entity().entity_id != self_entity.entity_id {
|
||||||
|
let player_base: Arc<dyn EntityBase> = player.clone();
|
||||||
|
dyn_self.push(&player_base).await;
|
||||||
|
pushed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
picked_up && !pushed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn on_hit(&self, _hit: crate::entity::projectile::ProjectileHit) -> EntityBaseFuture<'_, ()> {
|
fn on_hit(&self, _hit: crate::entity::projectile::ProjectileHit) -> EntityBaseFuture<'_, ()> {
|
||||||
Box::pin(async {})
|
Box::pin(async {})
|
||||||
}
|
}
|
||||||
@ -1766,7 +1993,11 @@ impl Entity {
|
|||||||
// Move by a delta, adjust for collisions, and send
|
// Move by a delta, adjust for collisions, and send
|
||||||
|
|
||||||
// Does not send movement. That must be done separately
|
// Does not send movement. That must be done separately
|
||||||
async fn move_entity<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, mut motion: Vector3<f64>) {
|
pub async fn move_entity<'a>(
|
||||||
|
&'a self,
|
||||||
|
caller: &'a Arc<dyn EntityBase>,
|
||||||
|
mut motion: Vector3<f64>,
|
||||||
|
) {
|
||||||
if caller.get_player().is_some() {
|
if caller.get_player().is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2764,9 +2995,6 @@ impl Entity {
|
|||||||
if let Some(passenger) = removed_passenger {
|
if let Some(passenger) = removed_passenger {
|
||||||
let vehicle_box = self.bounding_box.load();
|
let vehicle_box = self.bounding_box.load();
|
||||||
let passenger_entity = passenger.get_entity();
|
let passenger_entity = passenger.get_entity();
|
||||||
let passenger_yaw = passenger_entity.yaw.load();
|
|
||||||
let passenger_width = passenger_entity.entity_dimension.load().width as f64;
|
|
||||||
let vehicle_width = self.entity_dimension.load().width as f64;
|
|
||||||
|
|
||||||
// Pre-allocate teleport ID and block movement packets BEFORE sending
|
// Pre-allocate teleport ID and block movement packets BEFORE sending
|
||||||
// CSetPassengers. This prevents a race condition where the client receives
|
// CSetPassengers. This prevents a race condition where the client receives
|
||||||
@ -2806,28 +3034,75 @@ impl Entity {
|
|||||||
world.broadcast_to_chunk(chunk_pos, &passengers_packet);
|
world.broadcast_to_chunk(chunk_pos, &passengers_packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate dismount offset (vanilla getPassengerDismountOffset)
|
// Calculate dismount directions and offsets (vanilla DismountHelper)
|
||||||
let offset_dist =
|
let vehicle_yaw = self.yaw.load();
|
||||||
(vehicle_width * std::f64::consts::SQRT_2 + passenger_width + 0.00001) / 2.0;
|
// Wrap yaw to 0..360 range
|
||||||
let yaw_rad = (-passenger_yaw).to_radians();
|
let wrapped_yaw = (vehicle_yaw % 360.0 + 360.0) % 360.0;
|
||||||
let sin_yaw = f64::from(yaw_rad.sin());
|
let forward_dir = if !(45.0..315.0).contains(&wrapped_yaw) {
|
||||||
let cos_yaw = f64::from(yaw_rad.cos());
|
BlockDirection::South
|
||||||
let max_component = sin_yaw.abs().max(cos_yaw.abs());
|
} else if (45.0..135.0).contains(&wrapped_yaw) {
|
||||||
let offset_x = sin_yaw * offset_dist / max_component;
|
BlockDirection::West
|
||||||
let offset_z = cos_yaw * offset_dist / max_component;
|
} else if (135.0..225.0).contains(&wrapped_yaw) {
|
||||||
|
BlockDirection::North
|
||||||
|
} else {
|
||||||
|
BlockDirection::East
|
||||||
|
};
|
||||||
|
|
||||||
|
let get_step = |dir: BlockDirection| -> (i32, i32) {
|
||||||
|
match dir {
|
||||||
|
BlockDirection::North => (0, -1),
|
||||||
|
BlockDirection::South => (0, 1),
|
||||||
|
BlockDirection::East => (1, 0),
|
||||||
|
BlockDirection::West => (-1, 0),
|
||||||
|
_ => (0, 0),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let get_clockwise = |dir: BlockDirection| -> BlockDirection {
|
||||||
|
match dir {
|
||||||
|
BlockDirection::North => BlockDirection::East,
|
||||||
|
BlockDirection::East => BlockDirection::South,
|
||||||
|
BlockDirection::South => BlockDirection::West,
|
||||||
|
BlockDirection::West => BlockDirection::North,
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let get_opposite = |dir: BlockDirection| -> BlockDirection {
|
||||||
|
match dir {
|
||||||
|
BlockDirection::North => BlockDirection::South,
|
||||||
|
BlockDirection::South => BlockDirection::North,
|
||||||
|
BlockDirection::East => BlockDirection::West,
|
||||||
|
BlockDirection::West => BlockDirection::East,
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_dir = get_clockwise(forward_dir);
|
||||||
|
let left_dir = get_opposite(right_dir);
|
||||||
|
let back_dir = get_opposite(forward_dir);
|
||||||
|
|
||||||
|
let (fx, fz) = get_step(forward_dir);
|
||||||
|
let (rx, rz) = get_step(right_dir);
|
||||||
|
let (lx, lz) = get_step(left_dir);
|
||||||
|
let (bx, bz) = get_step(back_dir);
|
||||||
|
|
||||||
|
let offsets = [
|
||||||
|
(rx, rz),
|
||||||
|
(lx, lz),
|
||||||
|
(bx + rx, bz + rz),
|
||||||
|
(bx + lx, bz + lz),
|
||||||
|
(fx + rx, fz + rz),
|
||||||
|
(fx + lx, fz + lz),
|
||||||
|
(bx, bz),
|
||||||
|
(fx, fz),
|
||||||
|
];
|
||||||
|
|
||||||
let target_x = self.pos.load().x + offset_x;
|
|
||||||
let target_z = self.pos.load().z + offset_z;
|
|
||||||
let target_block_y = vehicle_box.max.y.floor() as i32;
|
let target_block_y = vehicle_box.max.y.floor() as i32;
|
||||||
let block_pos = BlockPos(Vector3::new(
|
|
||||||
target_x.floor() as i32,
|
|
||||||
target_block_y,
|
|
||||||
target_z.floor() as i32,
|
|
||||||
));
|
|
||||||
let below_pos = BlockPos(Vector3::new(
|
let below_pos = BlockPos(Vector3::new(
|
||||||
target_x.floor() as i32,
|
self.pos.load().x.floor() as i32,
|
||||||
target_block_y - 1,
|
target_block_y - 1,
|
||||||
target_z.floor() as i32,
|
self.pos.load().z.floor() as i32,
|
||||||
));
|
));
|
||||||
|
|
||||||
let below_state_id = world.get_block_state_id(&below_pos);
|
let below_state_id = world.get_block_state_id(&below_pos);
|
||||||
@ -2841,35 +3116,47 @@ impl Entity {
|
|||||||
let dismount_pos = if is_water {
|
let dismount_pos = if is_water {
|
||||||
fallback_pos
|
fallback_pos
|
||||||
} else {
|
} else {
|
||||||
// Vanilla tries two Y levels: at vehicle top and one block below
|
// Vanilla checks Standing, Crouching, Swimming poses and their respective height checks
|
||||||
let mut candidates = Vec::new();
|
let poses_and_heights = [
|
||||||
for pos in [&block_pos, &below_pos] {
|
(EntityPose::Standing, vec![0, 1, -1]),
|
||||||
let height = world.get_dismount_height(pos);
|
(EntityPose::Crouching, vec![0, 1, -1]),
|
||||||
// Vanilla: canDismountInBlock = !height.is_infinite() && height < 1.0
|
(EntityPose::Swimming, vec![0, 1]),
|
||||||
if height.is_finite() && height < 1.0 {
|
|
||||||
candidates.push(Vector3::new(
|
|
||||||
target_x,
|
|
||||||
f64::from(pos.0.y) + height,
|
|
||||||
target_z,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try poses: Standing, Crouching, Swimming (vanilla order)
|
|
||||||
let poses = [
|
|
||||||
EntityPose::Standing,
|
|
||||||
EntityPose::Crouching,
|
|
||||||
EntityPose::Swimming,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let vehicle_block_pos = self.block_pos.load();
|
||||||
let mut found = None;
|
let mut found = None;
|
||||||
'outer: for pose in poses {
|
|
||||||
|
'search: for (pose, y_offsets) in poses_and_heights {
|
||||||
let dims = Self::get_entity_dimensions(pose);
|
let dims = Self::get_entity_dimensions(pose);
|
||||||
for candidate in &candidates {
|
|
||||||
let bbox =
|
for y_offset in y_offsets {
|
||||||
BoundingBox::new_from_pos(candidate.x, candidate.y, candidate.z, &dims);
|
for &(ox, oz) in &offsets {
|
||||||
|
let target_block_x = vehicle_block_pos.0.x + ox;
|
||||||
|
let target_block_y = vehicle_block_pos.0.y + y_offset;
|
||||||
|
let target_block_z = vehicle_block_pos.0.z + oz;
|
||||||
|
|
||||||
|
let target_pos = BlockPos(Vector3::new(
|
||||||
|
target_block_x,
|
||||||
|
target_block_y,
|
||||||
|
target_block_z,
|
||||||
|
));
|
||||||
|
let height = world.get_dismount_height(&target_pos);
|
||||||
|
|
||||||
|
if height.is_finite() && height < 1.0 {
|
||||||
|
let location = Vector3::new(
|
||||||
|
f64::from(target_block_x) + 0.5,
|
||||||
|
f64::from(target_block_y) + height,
|
||||||
|
f64::from(target_block_z) + 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bbox = BoundingBox::new_from_pos(
|
||||||
|
location.x, location.y, location.z, &dims,
|
||||||
|
);
|
||||||
if world.is_space_empty(bbox) {
|
if world.is_space_empty(bbox) {
|
||||||
found = Some((*candidate, pose));
|
found = Some((location, pose));
|
||||||
break 'outer;
|
break 'search;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2879,9 +3166,43 @@ impl Entity {
|
|||||||
passenger_entity.set_pose(pose);
|
passenger_entity.set_pose(pose);
|
||||||
}
|
}
|
||||||
pos
|
pos
|
||||||
|
} else {
|
||||||
|
// Try dismounting directly on top of the vehicle as fallback
|
||||||
|
let mut found_fallback = None;
|
||||||
|
let vehicle_top = vehicle_box.max.y;
|
||||||
|
|
||||||
|
let poses = [
|
||||||
|
EntityPose::Standing,
|
||||||
|
EntityPose::Crouching,
|
||||||
|
EntityPose::Swimming,
|
||||||
|
];
|
||||||
|
|
||||||
|
for pose in poses {
|
||||||
|
let dims = Self::get_entity_dimensions(pose);
|
||||||
|
let bbox = BoundingBox::new_from_pos(
|
||||||
|
self.pos.load().x,
|
||||||
|
vehicle_top,
|
||||||
|
self.pos.load().z,
|
||||||
|
&dims,
|
||||||
|
);
|
||||||
|
if world.is_space_empty(bbox) {
|
||||||
|
found_fallback = Some((
|
||||||
|
Vector3::new(self.pos.load().x, vehicle_top, self.pos.load().z),
|
||||||
|
pose,
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((pos, pose)) = found_fallback {
|
||||||
|
if pose != EntityPose::Standing {
|
||||||
|
passenger_entity.set_pose(pose);
|
||||||
|
}
|
||||||
|
pos
|
||||||
} else {
|
} else {
|
||||||
fallback_pos
|
fallback_pos
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(player) = passenger.get_player() {
|
if let Some(player) = passenger.get_player() {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::f64::consts::TAU;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@ -452,6 +452,7 @@ pub struct Player {
|
|||||||
pub tick_counter: AtomicI32,
|
pub tick_counter: AtomicI32,
|
||||||
pub packet_sequence: AtomicI32,
|
pub packet_sequence: AtomicI32,
|
||||||
pub mining_pos: Mutex<BlockPos>,
|
pub mining_pos: Mutex<BlockPos>,
|
||||||
|
pub last_input: AtomicI8,
|
||||||
/// A counter for teleport IDs used to track pending teleports.
|
/// A counter for teleport IDs used to track pending teleports.
|
||||||
pub teleport_id_count: AtomicI32,
|
pub teleport_id_count: AtomicI32,
|
||||||
/// The pending teleport information, including the teleport ID and target location.
|
/// The pending teleport information, including the teleport ID and target location.
|
||||||
@ -645,6 +646,7 @@ impl Player {
|
|||||||
tick_counter: AtomicI32::new(0),
|
tick_counter: AtomicI32::new(0),
|
||||||
packet_sequence: AtomicI32::new(-1),
|
packet_sequence: AtomicI32::new(-1),
|
||||||
start_mining_time: AtomicI32::new(0),
|
start_mining_time: AtomicI32::new(0),
|
||||||
|
last_input: AtomicI8::new(0),
|
||||||
carried_item: Mutex::new(None),
|
carried_item: Mutex::new(None),
|
||||||
experience_pick_up_delay: Mutex::new(0),
|
experience_pick_up_delay: Mutex::new(0),
|
||||||
teleport_id_count: AtomicI32::new(0),
|
teleport_id_count: AtomicI32::new(0),
|
||||||
@ -4471,6 +4473,10 @@ impl EntityBase for Player {
|
|||||||
self.gamemode.load() == GameMode::Spectator
|
self.gamemode.load() == GameMode::Spectator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_pushable(&self) -> bool {
|
||||||
|
self.gamemode.load() != GameMode::Spectator && self.gamemode.load() != GameMode::Creative
|
||||||
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> TextComponent {
|
fn get_name(&self) -> TextComponent {
|
||||||
//TODO: team color
|
//TODO: team color
|
||||||
TextComponent::text(self.gameprofile.name.clone())
|
TextComponent::text(self.gameprofile.name.clone())
|
||||||
|
|||||||
@ -406,17 +406,16 @@ impl EntityBase for ArrowEntity {
|
|||||||
|
|
||||||
if target.get_living_entity().is_some() {
|
if target.get_living_entity().is_some() {
|
||||||
let punch = self.punch_level.load(Ordering::Relaxed);
|
let punch = self.punch_level.load(Ordering::Relaxed);
|
||||||
if punch > 0 {
|
if punch > 0
|
||||||
if let Some(owner_id) = self.owner_id {
|
&& let Some(owner_id) = self.owner_id
|
||||||
if let Some(owner_entity) = world.get_entity_by_id(owner_id) {
|
&& let Some(owner_entity) = world.get_entity_by_id(owner_id)
|
||||||
|
{
|
||||||
crate::entity::combat::handle_knockback(
|
crate::entity::combat::handle_knockback(
|
||||||
owner_entity.get_entity(),
|
owner_entity.get_entity(),
|
||||||
target.get_entity(),
|
target.get_entity(),
|
||||||
f64::from(punch) * 0.6,
|
f64::from(punch) * 0.6,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play hit sound
|
// Play hit sound
|
||||||
let sound_packet = CSoundEffect::new(
|
let sound_packet = CSoundEffect::new(
|
||||||
|
|||||||
@ -284,11 +284,11 @@ impl EntityBase for TridentEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle hit
|
// Handle hit
|
||||||
if let Some(h) = hit {
|
if let Some(h) = hit
|
||||||
if !self.has_hit.swap(true, Ordering::SeqCst) {
|
&& !self.has_hit.swap(true, Ordering::SeqCst)
|
||||||
|
{
|
||||||
caller.on_hit(h).await;
|
caller.on_hit(h).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,6 +109,7 @@ use crate::entity::projectile::trident::TridentEntity;
|
|||||||
use crate::entity::projectile::wind_charge::{WIND_CHARGE_GRAVITY, WindChargeEntity};
|
use crate::entity::projectile::wind_charge::{WIND_CHARGE_GRAVITY, WindChargeEntity};
|
||||||
use crate::entity::tnt::TNTEntity;
|
use crate::entity::tnt::TNTEntity;
|
||||||
use crate::entity::vehicle::boat::BoatEntity;
|
use crate::entity::vehicle::boat::BoatEntity;
|
||||||
|
use crate::entity::vehicle::minecart::MinecartEntity;
|
||||||
use crate::entity::{Entity, EntityBase, mob};
|
use crate::entity::{Entity, EntityBase, mob};
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
use pumpkin_data::Block;
|
use pumpkin_data::Block;
|
||||||
@ -240,6 +241,16 @@ pub fn from_type(
|
|||||||
id if id == EntityType::ARROW.id => Arc::new(ArrowEntity::new(entity, None)),
|
id if id == EntityType::ARROW.id => Arc::new(ArrowEntity::new(entity, None)),
|
||||||
id if id == EntityType::SPECTRAL_ARROW.id => Arc::new(ArrowEntity::new(entity, None)),
|
id if id == EntityType::SPECTRAL_ARROW.id => Arc::new(ArrowEntity::new(entity, None)),
|
||||||
id if id == EntityType::TRIDENT.id => Arc::new(TridentEntity::new(entity, None)),
|
id if id == EntityType::TRIDENT.id => Arc::new(TridentEntity::new(entity, None)),
|
||||||
|
id if id == EntityType::MINECART.id
|
||||||
|
|| id == EntityType::CHEST_MINECART.id
|
||||||
|
|| id == EntityType::FURNACE_MINECART.id
|
||||||
|
|| id == EntityType::TNT_MINECART.id
|
||||||
|
|| id == EntityType::HOPPER_MINECART.id
|
||||||
|
|| id == EntityType::COMMAND_BLOCK_MINECART.id
|
||||||
|
|| id == EntityType::SPAWNER_MINECART.id =>
|
||||||
|
{
|
||||||
|
Arc::new(MinecartEntity::new(entity))
|
||||||
|
}
|
||||||
id if id == EntityType::FIREBALL.id => Arc::new(FireballEntity::new(entity)),
|
id if id == EntityType::FIREBALL.id => Arc::new(FireballEntity::new(entity)),
|
||||||
id if id == EntityType::SMALL_FIREBALL.id => Arc::new(SmallFireballEntity::new(entity)),
|
id if id == EntityType::SMALL_FIREBALL.id => Arc::new(SmallFireballEntity::new(entity)),
|
||||||
id if id == EntityType::WIND_CHARGE.id => {
|
id if id == EntityType::WIND_CHARGE.id => {
|
||||||
|
|||||||
@ -1,26 +1,25 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
|
||||||
use crate::entity::player::Player;
|
use crate::entity::player::Player;
|
||||||
use crate::entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity};
|
use crate::entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity};
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use crate::world::loot::{LootContextParameters, LootTableExt};
|
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::meta_data_type::MetaDataType;
|
use pumpkin_data::meta_data_type::MetaDataType;
|
||||||
use pumpkin_data::tracked_data::TrackedData;
|
use pumpkin_data::tracked_data::TrackedData;
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
|
||||||
use pumpkin_protocol::java::client::play::Metadata;
|
use pumpkin_protocol::java::client::play::Metadata;
|
||||||
use pumpkin_util::GameMode;
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
|
||||||
|
use crate::entity::vehicle::vehicle::VehicleEntity;
|
||||||
|
|
||||||
pub struct BoatEntity {
|
pub struct BoatEntity {
|
||||||
entity: Entity,
|
pub vehicle: VehicleEntity,
|
||||||
damage_wobble_ticks: AtomicI32,
|
|
||||||
damage_wobble_side: AtomicI32,
|
|
||||||
damage_wobble_strength: AtomicCell<f32>,
|
|
||||||
ticks_underwater: AtomicCell<f32>,
|
ticks_underwater: AtomicCell<f32>,
|
||||||
left_paddle_moving: AtomicBool,
|
left_paddle_moving: AtomicBool,
|
||||||
right_paddle_moving: AtomicBool,
|
right_paddle_moving: AtomicBool,
|
||||||
@ -29,10 +28,7 @@ pub struct BoatEntity {
|
|||||||
impl BoatEntity {
|
impl BoatEntity {
|
||||||
pub const fn new(entity: Entity) -> Self {
|
pub const fn new(entity: Entity) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity,
|
vehicle: VehicleEntity::new(entity),
|
||||||
damage_wobble_ticks: AtomicI32::new(0),
|
|
||||||
damage_wobble_side: AtomicI32::new(1),
|
|
||||||
damage_wobble_strength: AtomicCell::new(0.0),
|
|
||||||
ticks_underwater: AtomicCell::new(0.0),
|
ticks_underwater: AtomicCell::new(0.0),
|
||||||
left_paddle_moving: AtomicBool::new(false),
|
left_paddle_moving: AtomicBool::new(false),
|
||||||
right_paddle_moving: AtomicBool::new(false),
|
right_paddle_moving: AtomicBool::new(false),
|
||||||
@ -43,45 +39,14 @@ impl BoatEntity {
|
|||||||
self.left_paddle_moving.store(left, Ordering::Relaxed);
|
self.left_paddle_moving.store(left, Ordering::Relaxed);
|
||||||
self.right_paddle_moving.store(right, Ordering::Relaxed);
|
self.right_paddle_moving.store(right, Ordering::Relaxed);
|
||||||
|
|
||||||
self.entity.send_meta_data(&[
|
self.vehicle.entity.send_meta_data(&[
|
||||||
Metadata::new(TrackedData::ID_PADDLE_LEFT, MetaDataType::BOOLEAN, left),
|
Metadata::new(TrackedData::ID_PADDLE_LEFT, MetaDataType::BOOLEAN, left),
|
||||||
Metadata::new(TrackedData::ID_PADDLE_RIGHT, MetaDataType::BOOLEAN, right),
|
Metadata::new(TrackedData::ID_PADDLE_RIGHT, MetaDataType::BOOLEAN, right),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_wobble_metadata(&self) {
|
fn send_wobble_metadata(&self) {
|
||||||
self.entity.send_meta_data(&[
|
self.vehicle.send_wobble_metadata();
|
||||||
Metadata::new(
|
|
||||||
TrackedData::ID_HURT,
|
|
||||||
MetaDataType::INTEGER,
|
|
||||||
VarInt(self.damage_wobble_ticks.load(Ordering::Relaxed)),
|
|
||||||
),
|
|
||||||
Metadata::new(
|
|
||||||
TrackedData::ID_HURTDIR,
|
|
||||||
MetaDataType::INTEGER,
|
|
||||||
VarInt(self.damage_wobble_side.load(Ordering::Relaxed)),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
self.entity.send_meta_data(&[Metadata::new(
|
|
||||||
TrackedData::ID_DAMAGE,
|
|
||||||
MetaDataType::FLOAT,
|
|
||||||
self.damage_wobble_strength.load(),
|
|
||||||
)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async 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 params = LootContextParameters::default();
|
|
||||||
for stack in loot_table.get_loot(params) {
|
|
||||||
world.drop_stack(&pos, stack).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.entity.remove().await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +54,7 @@ impl NBTStorage for BoatEntity {}
|
|||||||
|
|
||||||
impl EntityBase for BoatEntity {
|
impl EntityBase for BoatEntity {
|
||||||
fn get_entity(&self) -> &Entity {
|
fn get_entity(&self) -> &Entity {
|
||||||
&self.entity
|
&self.vehicle.entity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||||
@ -102,18 +67,10 @@ impl EntityBase for BoatEntity {
|
|||||||
_server: &'a Server,
|
_server: &'a Server,
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ticks = self.damage_wobble_ticks.load(Ordering::Relaxed);
|
self.vehicle.tick();
|
||||||
if ticks > 0 {
|
|
||||||
self.damage_wobble_ticks.store(ticks - 1, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
let strength = self.damage_wobble_strength.load();
|
|
||||||
if strength > 0.0 {
|
|
||||||
self.damage_wobble_strength.store(strength - 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let underwater = self.ticks_underwater.load();
|
let underwater = self.ticks_underwater.load();
|
||||||
if self.entity.touching_water.load(Ordering::Relaxed) {
|
if self.vehicle.entity.touching_water.load(Ordering::Relaxed) {
|
||||||
self.ticks_underwater.store((underwater + 1.0).min(60.0));
|
self.ticks_underwater.store((underwater + 1.0).min(60.0));
|
||||||
} else if underwater > 0.0 {
|
} else if underwater > 0.0 {
|
||||||
self.ticks_underwater.store((underwater - 1.0).max(0.0));
|
self.ticks_underwater.store((underwater - 1.0).max(0.0));
|
||||||
@ -128,7 +85,7 @@ impl EntityBase for BoatEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn can_hit(&self) -> bool {
|
fn can_hit(&self) -> bool {
|
||||||
self.entity.is_alive()
|
self.vehicle.entity.is_alive()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_collidable(&self, _entity: Option<Box<dyn EntityBase>>) -> bool {
|
fn is_collidable(&self, _entity: Option<Box<dyn EntityBase>>) -> bool {
|
||||||
@ -144,37 +101,7 @@ impl EntityBase for BoatEntity {
|
|||||||
source: Option<&'a dyn EntityBase>,
|
source: Option<&'a dyn EntityBase>,
|
||||||
_cause: Option<&'a dyn EntityBase>,
|
_cause: Option<&'a dyn EntityBase>,
|
||||||
) -> EntityBaseFuture<'a, bool> {
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
Box::pin(async move {
|
Box::pin(async move { self.vehicle.damage_with_context(amount, source).await })
|
||||||
if !self.entity.is_alive() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_side = self.damage_wobble_side.load(Ordering::Relaxed);
|
|
||||||
self.damage_wobble_side
|
|
||||||
.store(-current_side, Ordering::Relaxed);
|
|
||||||
self.damage_wobble_ticks.store(10, Ordering::Relaxed);
|
|
||||||
self.entity.velocity_dirty.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
let current_strength = self.damage_wobble_strength.load();
|
|
||||||
let new_strength = current_strength + amount * 10.0;
|
|
||||||
self.damage_wobble_strength.store(new_strength);
|
|
||||||
|
|
||||||
self.send_wobble_metadata();
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if is_creative {
|
|
||||||
self.entity.remove().await;
|
|
||||||
} else {
|
|
||||||
self.kill_and_drop_self().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interact<'a>(
|
fn interact<'a>(
|
||||||
@ -191,7 +118,7 @@ impl EntityBase for BoatEntity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.entity.passengers.lock().await.len() >= 2 {
|
if self.vehicle.entity.passengers.lock().await.len() >= 2 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,8 +126,8 @@ impl EntityBase for BoatEntity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let world = self.entity.world.load();
|
let world = self.vehicle.entity.world.load();
|
||||||
let Some(vehicle) = world.get_entity_by_id(self.entity.entity_id) else {
|
let Some(vehicle) = world.get_entity_by_id(self.vehicle.entity.entity_id) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -208,7 +135,8 @@ impl EntityBase for BoatEntity {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.entity
|
self.vehicle
|
||||||
|
.entity
|
||||||
.add_passenger(vehicle, passenger as Arc<dyn EntityBase>)
|
.add_passenger(vehicle, passenger as Arc<dyn EntityBase>)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -229,4 +157,8 @@ impl EntityBase for BoatEntity {
|
|||||||
fn cast_any(&self) -> &dyn std::any::Any {
|
fn cast_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_pushable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
679
pumpkin/src/entity/vehicle/minecart.rs
Normal file
679
pumpkin/src/entity/vehicle/minecart.rs
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||||
|
|
||||||
|
use pumpkin_protocol::java::server::play::SPlayerInput;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
entity::{
|
||||||
|
Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity, player::Player,
|
||||||
|
},
|
||||||
|
server::Server,
|
||||||
|
};
|
||||||
|
use pumpkin_data::Block;
|
||||||
|
use pumpkin_data::block_properties::{BlockProperties, PoweredRailLikeProperties};
|
||||||
|
use pumpkin_data::entity::EntityType;
|
||||||
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
|
||||||
|
use crate::entity::vehicle::vehicle::VehicleEntity;
|
||||||
|
|
||||||
|
const fn get_exits(
|
||||||
|
shape: pumpkin_data::block_properties::RailShape,
|
||||||
|
) -> (Vector3<f64>, Vector3<f64>) {
|
||||||
|
use pumpkin_data::block_properties::RailShape;
|
||||||
|
match shape {
|
||||||
|
RailShape::NorthSouth => (Vector3::new(0.0, 0.0, -1.0), Vector3::new(0.0, 0.0, 1.0)),
|
||||||
|
RailShape::EastWest => (Vector3::new(-1.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0)),
|
||||||
|
RailShape::AscendingEast => (Vector3::new(-1.0, 0.0, 0.0), Vector3::new(1.0, 1.0, 0.0)),
|
||||||
|
RailShape::AscendingWest => (Vector3::new(1.0, 0.0, 0.0), Vector3::new(-1.0, 1.0, 0.0)),
|
||||||
|
RailShape::AscendingNorth => (Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.0, 1.0, -1.0)),
|
||||||
|
RailShape::AscendingSouth => (Vector3::new(0.0, 0.0, -1.0), Vector3::new(0.0, 1.0, 1.0)),
|
||||||
|
RailShape::SouthEast => (Vector3::new(0.0, 0.0, 1.0), Vector3::new(1.0, 0.0, 0.0)),
|
||||||
|
RailShape::SouthWest => (Vector3::new(0.0, 0.0, 1.0), Vector3::new(-1.0, 0.0, 0.0)),
|
||||||
|
RailShape::NorthWest => (Vector3::new(0.0, 0.0, -1.0), Vector3::new(-1.0, 0.0, 0.0)),
|
||||||
|
RailShape::NorthEast => (Vector3::new(0.0, 0.0, -1.0), Vector3::new(1.0, 0.0, 0.0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MinecartEntity {
|
||||||
|
pub vehicle: VehicleEntity,
|
||||||
|
pub active: AtomicBool,
|
||||||
|
pub tnt_fuse: AtomicI32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinecartEntity {
|
||||||
|
pub const fn new(entity: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
vehicle: VehicleEntity::new(entity),
|
||||||
|
active: AtomicBool::new(false),
|
||||||
|
tnt_fuse: AtomicI32::new(-1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NBTStorage for MinecartEntity {}
|
||||||
|
|
||||||
|
impl EntityBase for MinecartEntity {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn tick<'a>(
|
||||||
|
&'a self,
|
||||||
|
caller: &'a Arc<dyn EntityBase>,
|
||||||
|
_server: &'a Server,
|
||||||
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.vehicle.tick();
|
||||||
|
|
||||||
|
let world = self.vehicle.entity.world.load();
|
||||||
|
let pos = self.vehicle.entity.pos.load();
|
||||||
|
let mut block_pos = BlockPos(Vector3::new(
|
||||||
|
pos.x.floor() as i32,
|
||||||
|
pos.y.floor() as i32,
|
||||||
|
pos.z.floor() as i32,
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut block = world.get_block(&block_pos);
|
||||||
|
let mut state_id = world.get_block_state_id(&block_pos);
|
||||||
|
|
||||||
|
let mut is_powered_rail = block.id == Block::POWERED_RAIL.id;
|
||||||
|
let mut is_activator_rail = block.id == Block::ACTIVATOR_RAIL.id;
|
||||||
|
let mut is_on_rails = is_powered_rail
|
||||||
|
|| is_activator_rail
|
||||||
|
|| block.id == Block::RAIL.id
|
||||||
|
|| block.id == Block::DETECTOR_RAIL.id;
|
||||||
|
|
||||||
|
// If not on rails at current Y level, check the block directly below
|
||||||
|
if !is_on_rails {
|
||||||
|
let below_block_pos = BlockPos(Vector3::new(
|
||||||
|
block_pos.0.x,
|
||||||
|
block_pos.0.y - 1,
|
||||||
|
block_pos.0.z,
|
||||||
|
));
|
||||||
|
let below_block = world.get_block(&below_block_pos);
|
||||||
|
if below_block.id == Block::RAIL.id
|
||||||
|
|| below_block.id == Block::POWERED_RAIL.id
|
||||||
|
|| below_block.id == Block::DETECTOR_RAIL.id
|
||||||
|
|| below_block.id == Block::ACTIVATOR_RAIL.id
|
||||||
|
{
|
||||||
|
block_pos = below_block_pos;
|
||||||
|
block = below_block;
|
||||||
|
state_id = world.get_block_state_id(&block_pos);
|
||||||
|
is_powered_rail = block.id == Block::POWERED_RAIL.id;
|
||||||
|
is_activator_rail = block.id == Block::ACTIVATOR_RAIL.id;
|
||||||
|
is_on_rails = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_powered_rail || is_activator_rail {
|
||||||
|
let props = PoweredRailLikeProperties::from_state_id(state_id, block);
|
||||||
|
let powered = props.powered;
|
||||||
|
|
||||||
|
self.active.store(powered, Ordering::Relaxed);
|
||||||
|
|
||||||
|
if powered {
|
||||||
|
if is_powered_rail {
|
||||||
|
let mut velocity = self.vehicle.entity.velocity.load();
|
||||||
|
let speed = velocity.length();
|
||||||
|
if speed > 0.01 {
|
||||||
|
let new_speed = (speed + 0.06).min(0.4);
|
||||||
|
velocity = velocity
|
||||||
|
.normalize()
|
||||||
|
.multiply(new_speed, new_speed, new_speed);
|
||||||
|
self.vehicle.entity.velocity.store(velocity);
|
||||||
|
} else {
|
||||||
|
let yaw = self.vehicle.entity.yaw.load();
|
||||||
|
let push_dir = Vector3::new(
|
||||||
|
-f64::from((yaw.to_radians()).sin()),
|
||||||
|
0.0,
|
||||||
|
f64::from((yaw.to_radians()).cos()),
|
||||||
|
);
|
||||||
|
self.vehicle
|
||||||
|
.entity
|
||||||
|
.velocity
|
||||||
|
.store(push_dir.multiply(0.1, 0.1, 0.1));
|
||||||
|
}
|
||||||
|
self.vehicle.entity.send_velocity();
|
||||||
|
} else if is_activator_rail {
|
||||||
|
let passengers = self.vehicle.entity.passengers.lock().await.clone();
|
||||||
|
for passenger in passengers {
|
||||||
|
let passenger_id = passenger.get_entity().entity_id;
|
||||||
|
self.vehicle.entity.remove_passenger(passenger_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.vehicle.entity.entity_type.id == EntityType::TNT_MINECART.id {
|
||||||
|
let fuse = self.tnt_fuse.load(Ordering::Relaxed);
|
||||||
|
if fuse == -1 {
|
||||||
|
self.tnt_fuse.store(80, Ordering::Relaxed);
|
||||||
|
world.play_sound(
|
||||||
|
pumpkin_data::sound::Sound::EntityTntPrimed,
|
||||||
|
pumpkin_data::sound::SoundCategory::Blocks,
|
||||||
|
&pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.vehicle.get_hurt_time() == 0 {
|
||||||
|
self.vehicle.set_hurt_dir(-self.vehicle.get_hurt_dir());
|
||||||
|
self.vehicle.set_hurt_time(10);
|
||||||
|
self.vehicle.set_damage(50.0);
|
||||||
|
// TODO: Send entity status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if is_powered_rail {
|
||||||
|
let mut velocity = self.vehicle.entity.velocity.load();
|
||||||
|
velocity = velocity.multiply(0.5, 0.5, 0.5);
|
||||||
|
if velocity.length() < 0.01 {
|
||||||
|
velocity = Vector3::new(0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
self.vehicle.entity.velocity.store(velocity);
|
||||||
|
self.vehicle.entity.send_velocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.vehicle.entity.entity_type.id == EntityType::TNT_MINECART.id {
|
||||||
|
let fuse = self.tnt_fuse.load(Ordering::Relaxed);
|
||||||
|
if fuse > 0 {
|
||||||
|
let new_fuse = fuse - 1;
|
||||||
|
self.tnt_fuse.store(new_fuse, Ordering::Relaxed);
|
||||||
|
|
||||||
|
if new_fuse % 2 == 0 {
|
||||||
|
world.spawn_particle(
|
||||||
|
pos,
|
||||||
|
Vector3::new(0.0, 0.0, 0.0),
|
||||||
|
0.0,
|
||||||
|
1,
|
||||||
|
pumpkin_data::particle::Particle::Smoke,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_fuse == 0 {
|
||||||
|
self.vehicle.entity.remove().await;
|
||||||
|
world.explode(pos, 4.0).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut velocity = self.vehicle.entity.velocity.load();
|
||||||
|
|
||||||
|
let mut has_driver = false;
|
||||||
|
let mut driver_input = 0;
|
||||||
|
let mut driver_yaw = 0.0f32;
|
||||||
|
|
||||||
|
{
|
||||||
|
let passengers = self.vehicle.entity.passengers.lock().await;
|
||||||
|
if let Some(passenger) = passengers.first()
|
||||||
|
&& let Some(player) = passenger.get_player()
|
||||||
|
{
|
||||||
|
driver_input = player.last_input.load(Ordering::Relaxed);
|
||||||
|
driver_yaw = player.get_entity().yaw.load();
|
||||||
|
has_driver = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_driver && is_on_rails {
|
||||||
|
let forward = driver_input & SPlayerInput::FORWARD != 0;
|
||||||
|
let backward = driver_input & SPlayerInput::BACKWARD != 0;
|
||||||
|
|
||||||
|
let mut force_dir = Vector3::new(0.0, 0.0, 0.0);
|
||||||
|
if forward {
|
||||||
|
let yaw_rad = f64::from(driver_yaw).to_radians();
|
||||||
|
force_dir.x = -yaw_rad.sin();
|
||||||
|
force_dir.z = yaw_rad.cos();
|
||||||
|
} else if backward {
|
||||||
|
let yaw_rad = f64::from(driver_yaw).to_radians();
|
||||||
|
force_dir.x = yaw_rad.sin();
|
||||||
|
force_dir.z = -yaw_rad.cos();
|
||||||
|
}
|
||||||
|
|
||||||
|
if forward || backward {
|
||||||
|
velocity.x += force_dir.x * 0.02;
|
||||||
|
velocity.z += force_dir.z * 0.02;
|
||||||
|
|
||||||
|
let speed = velocity.x.hypot(velocity.z);
|
||||||
|
if speed > 0.15 {
|
||||||
|
#[allow(clippy::suboptimal_flops)]
|
||||||
|
let old_speed = self
|
||||||
|
.vehicle
|
||||||
|
.entity
|
||||||
|
.velocity
|
||||||
|
.load()
|
||||||
|
.x
|
||||||
|
.hypot(self.vehicle.entity.velocity.load().z);
|
||||||
|
|
||||||
|
let max_speed = old_speed.clamp(0.15, 0.4);
|
||||||
|
if speed > max_speed {
|
||||||
|
velocity.x = (velocity.x / speed) * max_speed;
|
||||||
|
velocity.z = (velocity.z / speed) * max_speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.vehicle.entity.velocity.store(velocity);
|
||||||
|
self.vehicle.entity.send_velocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut velocity = self.vehicle.entity.velocity.load();
|
||||||
|
|
||||||
|
if is_on_rails {
|
||||||
|
use pumpkin_data::block_properties::RailLikeProperties;
|
||||||
|
use pumpkin_data::block_properties::{RailShape, RailShapeStraight};
|
||||||
|
|
||||||
|
let shape = if block.id == Block::RAIL.id {
|
||||||
|
let props = RailLikeProperties::from_state_id(state_id, block);
|
||||||
|
props.shape
|
||||||
|
} else {
|
||||||
|
let props = PoweredRailLikeProperties::from_state_id(state_id, block);
|
||||||
|
match props.shape {
|
||||||
|
RailShapeStraight::NorthSouth => RailShape::NorthSouth,
|
||||||
|
RailShapeStraight::EastWest => RailShape::EastWest,
|
||||||
|
RailShapeStraight::AscendingEast => RailShape::AscendingEast,
|
||||||
|
RailShapeStraight::AscendingWest => RailShape::AscendingWest,
|
||||||
|
RailShapeStraight::AscendingNorth => RailShape::AscendingNorth,
|
||||||
|
RailShapeStraight::AscendingSouth => RailShape::AscendingSouth,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let pos = self.vehicle.entity.pos.load();
|
||||||
|
let block_center_bottom = Vector3::new(
|
||||||
|
f64::from(block_pos.0.x) + 0.5,
|
||||||
|
f64::from(block_pos.0.y),
|
||||||
|
f64::from(block_pos.0.z) + 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (exit0, exit1) = get_exits(shape);
|
||||||
|
let exit0 = exit0.multiply(0.5, 0.5, 0.5);
|
||||||
|
let exit1 = exit1.multiply(0.5, 0.5, 0.5);
|
||||||
|
|
||||||
|
let in_corner = exit0.x != exit1.x && exit0.z != exit1.z;
|
||||||
|
let mut target_position = pos;
|
||||||
|
|
||||||
|
if in_corner {
|
||||||
|
let from0to1 = exit1 - exit0;
|
||||||
|
let from0topos = pos - block_center_bottom - exit0;
|
||||||
|
let dot_num = from0to1.dot(&from0topos);
|
||||||
|
let dot_den = from0to1.dot(&from0to1);
|
||||||
|
if dot_den != 0.0 {
|
||||||
|
let travel_vector_from0 = from0to1.multiply(
|
||||||
|
dot_num / dot_den,
|
||||||
|
dot_num / dot_den,
|
||||||
|
dot_num / dot_den,
|
||||||
|
);
|
||||||
|
target_position = block_center_bottom.add(&exit0).add(&travel_vector_from0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let z_snap = (exit0.x - exit1.x).abs() > 1e-5;
|
||||||
|
let x_snap = (exit0.z - exit1.z).abs() > 1e-5;
|
||||||
|
if x_snap {
|
||||||
|
target_position.x = block_center_bottom.x;
|
||||||
|
}
|
||||||
|
if z_snap {
|
||||||
|
target_position.z = block_center_bottom.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target_position.y = pos.y;
|
||||||
|
self.vehicle.entity.pos.store(target_position);
|
||||||
|
|
||||||
|
let horizontal_in_direction = Vector3::new(exit1.x, 0.0, exit1.z);
|
||||||
|
let mut horizontal_out_direction = Vector3::new(exit0.x, 0.0, exit0.z);
|
||||||
|
|
||||||
|
if velocity.dot(&horizontal_out_direction) < velocity.dot(&horizontal_in_direction)
|
||||||
|
{
|
||||||
|
horizontal_out_direction = horizontal_in_direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_position = block_center_bottom.add(&horizontal_out_direction).add(
|
||||||
|
&horizontal_out_direction
|
||||||
|
.normalize()
|
||||||
|
.multiply(1e-5, 1e-5, 1e-5),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut towards_out = out_position - target_position;
|
||||||
|
towards_out.y = 0.0;
|
||||||
|
let towards_length = towards_out.length();
|
||||||
|
if towards_length > 1e-5 {
|
||||||
|
towards_out = towards_out.normalize();
|
||||||
|
let speed = velocity.length();
|
||||||
|
velocity = towards_out.multiply(speed, speed, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.vehicle.entity.velocity.store(velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if velocity.length() > 0.001 {
|
||||||
|
self.move_entity(caller, velocity).await;
|
||||||
|
let new_pos = self.vehicle.entity.pos.load();
|
||||||
|
|
||||||
|
let passengers = self.vehicle.entity.passengers.lock().await;
|
||||||
|
for passenger in passengers.iter() {
|
||||||
|
passenger.get_entity().set_pos(new_pos);
|
||||||
|
}
|
||||||
|
drop(passengers);
|
||||||
|
|
||||||
|
self.vehicle.entity.send_pos_rot();
|
||||||
|
|
||||||
|
#[allow(clippy::useless_let_if_seq)]
|
||||||
|
let mut friction = 0.98; // Default off-rail air drag
|
||||||
|
|
||||||
|
if is_on_rails {
|
||||||
|
let passengers = self.vehicle.entity.passengers.lock().await;
|
||||||
|
let has_passengers = !passengers.is_empty();
|
||||||
|
drop(passengers);
|
||||||
|
friction = if has_passengers { 0.99 } else { 0.96 };
|
||||||
|
} else {
|
||||||
|
let below_block_pos = BlockPos(Vector3::new(
|
||||||
|
block_pos.0.x,
|
||||||
|
block_pos.0.y - 1,
|
||||||
|
block_pos.0.z,
|
||||||
|
));
|
||||||
|
let below_block = world.get_block(&below_block_pos);
|
||||||
|
|
||||||
|
let is_on_ground = self.vehicle.entity.on_ground.load(Ordering::Relaxed)
|
||||||
|
|| (below_block.id != Block::AIR.id
|
||||||
|
&& below_block.id != Block::WATER.id
|
||||||
|
&& below_block.id != Block::LAVA.id);
|
||||||
|
let is_in_water = self.vehicle.entity.touching_water.load(Ordering::Relaxed)
|
||||||
|
|| below_block.id == Block::WATER.id;
|
||||||
|
|
||||||
|
if is_on_ground {
|
||||||
|
friction = 0.5;
|
||||||
|
} else if is_in_water {
|
||||||
|
friction = 0.95;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next_vel = velocity.multiply(friction, friction, friction);
|
||||||
|
if next_vel.length() < 0.005 {
|
||||||
|
next_vel = Vector3::new(0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
self.vehicle.entity.velocity.store(next_vel);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entity(&self) -> &Entity {
|
||||||
|
&self.vehicle.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_pushable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn push<'a>(&'a self, entity: &'a Arc<dyn EntityBase>) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let self_entity = self.get_entity();
|
||||||
|
let other_entity = entity.get_entity();
|
||||||
|
|
||||||
|
if self_entity.no_clip.load(Ordering::Relaxed)
|
||||||
|
|| other_entity.no_clip.load(Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let passengers = self_entity.passengers.lock().await;
|
||||||
|
if passengers
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.get_entity().entity_id == other_entity.entity_id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let passengers = other_entity.passengers.lock().await;
|
||||||
|
if passengers
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.get_entity().entity_id == self_entity.entity_id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut xa = other_entity.pos.load().x - self_entity.pos.load().x;
|
||||||
|
let mut za = other_entity.pos.load().z - self_entity.pos.load().z;
|
||||||
|
let mut dd = xa * xa + za * za;
|
||||||
|
if dd >= 1.0E-4 {
|
||||||
|
dd = dd.sqrt();
|
||||||
|
xa /= dd;
|
||||||
|
za /= dd;
|
||||||
|
let mut pow = 1.0 / dd;
|
||||||
|
if pow > 1.0 {
|
||||||
|
pow = 1.0;
|
||||||
|
}
|
||||||
|
xa *= pow;
|
||||||
|
za *= pow;
|
||||||
|
xa *= 0.1;
|
||||||
|
za *= 0.1;
|
||||||
|
xa *= 0.5;
|
||||||
|
za *= 0.5;
|
||||||
|
|
||||||
|
let is_other_minecart = other_entity.entity_type.id == EntityType::MINECART.id
|
||||||
|
|| other_entity.entity_type.id == EntityType::CHEST_MINECART.id
|
||||||
|
|| other_entity.entity_type.id == EntityType::COMMAND_BLOCK_MINECART.id
|
||||||
|
|| other_entity.entity_type.id == EntityType::FURNACE_MINECART.id
|
||||||
|
|| other_entity.entity_type.id == EntityType::HOPPER_MINECART.id
|
||||||
|
|| other_entity.entity_type.id == EntityType::SPAWNER_MINECART.id
|
||||||
|
|| other_entity.entity_type.id == EntityType::TNT_MINECART.id;
|
||||||
|
|
||||||
|
if is_other_minecart {
|
||||||
|
let xo = self_entity.velocity.load().x;
|
||||||
|
let zo = self_entity.velocity.load().z;
|
||||||
|
|
||||||
|
let dir = Vector3::new(xo, 0.0, zo).normalize();
|
||||||
|
let facing = Vector3::new(
|
||||||
|
f64::from(self_entity.yaw.load().to_radians().cos()),
|
||||||
|
0.0,
|
||||||
|
f64::from(self_entity.yaw.load().to_radians().sin()),
|
||||||
|
)
|
||||||
|
.normalize();
|
||||||
|
|
||||||
|
let dot = dir.dot(&facing).abs();
|
||||||
|
if dot >= 0.8 {
|
||||||
|
let vel = self_entity.velocity.load();
|
||||||
|
let ovel = other_entity.velocity.load();
|
||||||
|
|
||||||
|
let is_self_furnace =
|
||||||
|
self_entity.entity_type.id == EntityType::FURNACE_MINECART.id;
|
||||||
|
let is_other_furnace =
|
||||||
|
other_entity.entity_type.id == EntityType::FURNACE_MINECART.id;
|
||||||
|
|
||||||
|
if is_other_furnace && !is_self_furnace {
|
||||||
|
self_entity.velocity.store(vel.multiply(0.2, 1.0, 0.2));
|
||||||
|
let mut new_self_vel = self_entity.velocity.load();
|
||||||
|
new_self_vel.x += ovel.x - xa;
|
||||||
|
new_self_vel.z += ovel.z - za;
|
||||||
|
self_entity.velocity.store(new_self_vel);
|
||||||
|
self_entity.send_velocity();
|
||||||
|
|
||||||
|
other_entity.velocity.store(ovel.multiply(0.95, 1.0, 0.95));
|
||||||
|
other_entity.send_velocity();
|
||||||
|
} else if !is_other_furnace && is_self_furnace {
|
||||||
|
other_entity.velocity.store(ovel.multiply(0.2, 1.0, 0.2));
|
||||||
|
let mut new_other_vel = other_entity.velocity.load();
|
||||||
|
new_other_vel.x += vel.x + xa;
|
||||||
|
new_other_vel.z += vel.z + za;
|
||||||
|
other_entity.velocity.store(new_other_vel);
|
||||||
|
other_entity.send_velocity();
|
||||||
|
|
||||||
|
self_entity.velocity.store(vel.multiply(0.95, 1.0, 0.95));
|
||||||
|
self_entity.send_velocity();
|
||||||
|
} else {
|
||||||
|
#[allow(clippy::manual_midpoint)]
|
||||||
|
let xdd = (ovel.x + vel.x) / 2.0;
|
||||||
|
#[allow(clippy::manual_midpoint)]
|
||||||
|
let zdd = (ovel.z + vel.z) / 2.0;
|
||||||
|
|
||||||
|
self_entity.velocity.store(vel.multiply(0.2, 1.0, 0.2));
|
||||||
|
let mut new_self_vel = self_entity.velocity.load();
|
||||||
|
new_self_vel.x += xdd - xa;
|
||||||
|
new_self_vel.z += zdd - za;
|
||||||
|
self_entity.velocity.store(new_self_vel);
|
||||||
|
self_entity.send_velocity();
|
||||||
|
|
||||||
|
other_entity.velocity.store(ovel.multiply(0.2, 1.0, 0.2));
|
||||||
|
let mut new_other_vel = other_entity.velocity.load();
|
||||||
|
new_other_vel.x += xdd + xa;
|
||||||
|
new_other_vel.z += zdd + za;
|
||||||
|
other_entity.velocity.store(new_other_vel);
|
||||||
|
other_entity.send_velocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !self_entity.has_passengers().await && self.is_pushable() {
|
||||||
|
let mut vel = self_entity.velocity.load();
|
||||||
|
vel.x -= xa;
|
||||||
|
vel.z -= za;
|
||||||
|
self_entity.velocity.store(vel);
|
||||||
|
self_entity.send_velocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !other_entity.has_passengers().await && entity.is_pushable() {
|
||||||
|
let mut vel = other_entity.velocity.load();
|
||||||
|
vel.x += xa / 4.0;
|
||||||
|
vel.z += za / 4.0;
|
||||||
|
other_entity.velocity.store(vel);
|
||||||
|
other_entity.send_velocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_collidable(&self, _entity: Option<Box<dyn EntityBase>>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.vehicle.send_wobble_metadata();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_hit(&self) -> bool {
|
||||||
|
self.vehicle.entity.is_alive()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn damage_with_context<'a>(
|
||||||
|
&'a self,
|
||||||
|
_caller: &'a dyn EntityBase,
|
||||||
|
amount: f32,
|
||||||
|
_damage_type: pumpkin_data::damage::DamageType,
|
||||||
|
_position: Option<Vector3<f64>>,
|
||||||
|
source: Option<&'a dyn EntityBase>,
|
||||||
|
_cause: Option<&'a dyn EntityBase>,
|
||||||
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
|
Box::pin(async move { self.vehicle.damage_with_context(amount, source).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interact<'a>(
|
||||||
|
&'a self,
|
||||||
|
player: &'a Arc<Player>,
|
||||||
|
_item_stack: &'a mut ItemStack,
|
||||||
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
|
Box::pin(async move {
|
||||||
|
if player.get_entity().is_sneaking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.vehicle.entity.passengers.lock().await.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.get_entity().has_vehicle().await {
|
||||||
|
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<dyn EntityBase>)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_player_collision<'a>(&'a self, player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
if self
|
||||||
|
.vehicle
|
||||||
|
.entity
|
||||||
|
.passengers
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.any(|passenger| passenger.get_entity().entity_id == player.entity_id())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.is_spectator() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player_pos = player.get_entity().pos.load();
|
||||||
|
let minecart_pos = self.vehicle.entity.pos.load();
|
||||||
|
|
||||||
|
let mut diff_x = minecart_pos.x - player_pos.x;
|
||||||
|
let mut diff_z = minecart_pos.z - player_pos.z;
|
||||||
|
|
||||||
|
let dist_sq = diff_x * diff_x + diff_z * diff_z;
|
||||||
|
if dist_sq > 0.0001 {
|
||||||
|
let dist = dist_sq.sqrt();
|
||||||
|
diff_x /= dist;
|
||||||
|
diff_z /= dist;
|
||||||
|
|
||||||
|
let push_force = 0.1;
|
||||||
|
let mut vel = self.vehicle.entity.velocity.load();
|
||||||
|
vel.x += diff_x * push_force;
|
||||||
|
vel.z += diff_z * push_force;
|
||||||
|
|
||||||
|
let horizontal_speed = vel.x.hypot(vel.z);
|
||||||
|
if horizontal_speed > 0.4 {
|
||||||
|
vel.x = (vel.x / horizontal_speed) * 0.4;
|
||||||
|
vel.z = (vel.z / horizontal_speed) * 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.vehicle.entity.velocity.store(vel);
|
||||||
|
self.vehicle.entity.send_velocity();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_entity<'a>(
|
||||||
|
&'a self,
|
||||||
|
caller: &'a Arc<dyn EntityBase>,
|
||||||
|
motion: Vector3<f64>,
|
||||||
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let to_position = self.vehicle.entity.pos.load().add(&motion);
|
||||||
|
self.vehicle.entity.move_entity(caller, motion).await;
|
||||||
|
let should_continue = self.push_entities(caller).await;
|
||||||
|
if should_continue {
|
||||||
|
let current_pos = self.vehicle.entity.pos.load();
|
||||||
|
let back_motion = to_position.sub(¤t_pos);
|
||||||
|
self.vehicle.entity.move_entity(caller, back_motion).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,4 @@
|
|||||||
pub mod boat;
|
pub mod boat;
|
||||||
|
pub mod minecart;
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
|
pub mod vehicle;
|
||||||
|
|||||||
131
pumpkin/src/entity/vehicle/vehicle.rs
Normal file
131
pumpkin/src/entity/vehicle/vehicle.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
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_data::meta_data_type::MetaDataType;
|
||||||
|
use pumpkin_data::tracked_data::TrackedData;
|
||||||
|
use pumpkin_util::GameMode;
|
||||||
|
|
||||||
|
pub struct VehicleEntity {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub hurt_time: AtomicI32,
|
||||||
|
pub hurt_dir: AtomicI32,
|
||||||
|
pub damage: AtomicCell<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
TrackedData::ID_HURT,
|
||||||
|
MetaDataType::INTEGER,
|
||||||
|
self.get_hurt_time(),
|
||||||
|
),
|
||||||
|
Metadata::new(
|
||||||
|
TrackedData::ID_HURTDIR,
|
||||||
|
MetaDataType::INTEGER,
|
||||||
|
self.get_hurt_dir(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
self.entity.send_meta_data(&[Metadata::new(
|
||||||
|
TrackedData::ID_DAMAGE,
|
||||||
|
MetaDataType::FLOAT,
|
||||||
|
self.get_damage(),
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async 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 params = LootContextParameters::default();
|
||||||
|
for stack in loot_table.get_loot(params) {
|
||||||
|
world.drop_stack(&pos, stack).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.entity.remove().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn damage_with_context(&self, amount: f32, source: Option<&dyn EntityBase>) -> bool {
|
||||||
|
if !self.entity.is_alive() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if is_creative {
|
||||||
|
self.entity.remove().await;
|
||||||
|
} else {
|
||||||
|
self.kill_and_drop_self().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -173,8 +173,7 @@ impl BowItem {
|
|||||||
{
|
{
|
||||||
for (enchantment, level) in enchantments.enchantment.iter() {
|
for (enchantment, level) in enchantments.enchantment.iter() {
|
||||||
if **enchantment == pumpkin_data::Enchantment::POWER {
|
if **enchantment == pumpkin_data::Enchantment::POWER {
|
||||||
arrow.base_damage =
|
arrow.base_damage *= 1.0 + 0.25 * (f64::from(*level) + 1.0);
|
||||||
arrow.base_damage * (1.0 + 0.25 * (f64::from(*level) + 1.0));
|
|
||||||
} else if **enchantment == pumpkin_data::Enchantment::PUNCH {
|
} else if **enchantment == pumpkin_data::Enchantment::PUNCH {
|
||||||
arrow.punch_level.store(*level as u8, Ordering::Relaxed);
|
arrow.punch_level.store(*level as u8, Ordering::Relaxed);
|
||||||
} else if **enchantment == pumpkin_data::Enchantment::FLAME {
|
} else if **enchantment == pumpkin_data::Enchantment::FLAME {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::entity::Entity;
|
use crate::entity::Entity;
|
||||||
use crate::entity::player::Player;
|
use crate::entity::player::Player;
|
||||||
|
use crate::entity::vehicle::minecart::MinecartEntity;
|
||||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use pumpkin_data::BlockDirection;
|
use pumpkin_data::BlockDirection;
|
||||||
@ -80,12 +81,13 @@ impl ItemBehaviour for MinecartItem {
|
|||||||
let height = if is_ascending { 0.5 } else { 0.0 };
|
let height = if is_ascending { 0.5 } else { 0.0 };
|
||||||
let entity_type = Self::item_to_entity(item.item);
|
let entity_type = Self::item_to_entity(item.item);
|
||||||
let pos = location.to_f64();
|
let pos = location.to_f64();
|
||||||
let entity = Arc::new(Entity::new(
|
let entity = Entity::new(
|
||||||
world.clone(),
|
world.clone(),
|
||||||
Vector3::new(pos.x, pos.y + 0.0625 + height, pos.z),
|
Vector3::new(pos.x, pos.y + 0.0625 + height, pos.z),
|
||||||
entity_type,
|
entity_type,
|
||||||
));
|
);
|
||||||
world.spawn_entity(entity).await;
|
let minecart_entity = Arc::new(MinecartEntity::new(entity));
|
||||||
|
world.spawn_entity(minecart_entity).await;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -325,6 +325,9 @@ impl JavaClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if player.get_entity().has_vehicle().await {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
||||||
if player.awaiting_teleport.lock().await.is_some() {
|
if player.awaiting_teleport.lock().await.is_some() {
|
||||||
return;
|
return;
|
||||||
@ -455,6 +458,9 @@ impl JavaClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if player.get_entity().has_vehicle().await {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
// Ignore movement packets while awaiting a teleport confirmation (vanilla behavior)
|
||||||
if player.awaiting_teleport.lock().await.is_some() {
|
if player.awaiting_teleport.lock().await.is_some() {
|
||||||
return;
|
return;
|
||||||
@ -978,6 +984,8 @@ impl JavaClient {
|
|||||||
input: SPlayerInput,
|
input: SPlayerInput,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
) {
|
) {
|
||||||
|
player.last_input.store(input.input, Ordering::Relaxed);
|
||||||
|
|
||||||
let sneak = input.input & SPlayerInput::SNEAK != 0;
|
let sneak = input.input & SPlayerInput::SNEAK != 0;
|
||||||
if player.get_entity().is_sneaking() != sneak {
|
if player.get_entity().is_sneaking() != sneak {
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user