diff --git a/pumpkin-protocol/src/java/server/play/custom_payload.rs b/pumpkin-protocol/src/java/server/play/custom_payload.rs new file mode 100644 index 000000000..93a28cac3 --- /dev/null +++ b/pumpkin-protocol/src/java/server/play/custom_payload.rs @@ -0,0 +1,7 @@ +use pumpkin_data::packet::serverbound::PLAY_CUSTOM_PAYLOAD; +use pumpkin_macros::packet; + +#[packet(PLAY_CUSTOM_PAYLOAD)] +pub struct SCustomPayload { + // TODO +} diff --git a/pumpkin-protocol/src/java/server/play/mod.rs b/pumpkin-protocol/src/java/server/play/mod.rs index adb5df767..8c34a5fd7 100644 --- a/pumpkin-protocol/src/java/server/play/mod.rs +++ b/pumpkin-protocol/src/java/server/play/mod.rs @@ -10,6 +10,7 @@ mod close_container; mod command_suggestion; mod confirm_teleport; mod cookie_response; +mod custom_payload; mod interact; mod keep_alive; mod pick_item; @@ -44,6 +45,7 @@ pub use close_container::*; pub use command_suggestion::*; pub use confirm_teleport::*; pub use cookie_response::*; +pub use custom_payload::*; pub use interact::*; pub use keep_alive::*; pub use pick_item::*; diff --git a/pumpkin/src/entity/experience_orb.rs b/pumpkin/src/entity/experience_orb.rs index 45991b7f6..9720c1a49 100644 --- a/pumpkin/src/entity/experience_orb.rs +++ b/pumpkin/src/entity/experience_orb.rs @@ -76,7 +76,31 @@ impl ExperienceOrbEntity { #[async_trait] impl EntityBase for ExperienceOrbEntity { async fn tick(&self, caller: Arc, server: &Server) { - self.entity.tick(caller, server).await; + let entity = &self.entity; + entity.tick(caller.clone(), server).await; + let bounding_box = entity.bounding_box.load(); + + let original_velo = entity.velocity.load(); + + let mut velo = original_velo; + + let no_clip = !self + .entity + .world + .read() + .await + .is_space_empty(bounding_box.expand(-1.0e-7, -1.0e-7, -1.0e-7)) + .await; + // TODO: isSubmergedIn + if !no_clip { + velo.y -= self.get_gravity(); + } + + entity.velocity.store(velo); + + entity.move_entity(caller.clone(), velo).await; + + entity.tick_block_collisions(&caller, server).await; let age = self.orb_age.fetch_add(1, Ordering::Relaxed); if age >= 6000 { @@ -115,4 +139,8 @@ impl EntityBase for ExperienceOrbEntity { fn get_living_entity(&self) -> Option<&LivingEntity> { None } + + fn get_gravity(&self) -> f64 { + 0.03 + } } diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 4504af3de..720f26b46 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -1,8 +1,10 @@ use pumpkin_data::potion::Effect; use pumpkin_util::math::position::BlockPos; use std::sync::Arc; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::{AtomicU8, Ordering::Relaxed}; +use std::sync::atomic::{ + AtomicBool, AtomicU8, + Ordering::{Relaxed, SeqCst}, +}; use std::{collections::HashMap, sync::atomic::AtomicI32}; use super::{Entity, NBTStorage}; @@ -64,7 +66,7 @@ pub struct LivingEntity { /// The position where the entity was last climbing, used for death messages pub climbing_pos: AtomicCell>, - _water_movement_speed_multiplier: f32, + water_movement_speed_multiplier: f32, } #[async_trait] @@ -102,7 +104,7 @@ impl LivingEntity { climbing_pos: AtomicCell::new(None), movement_input: AtomicCell::new(Vector3::default()), movement_speed: AtomicCell::new(default_movement_speed), - _water_movement_speed_multiplier: water_movement_speed_multiplier, + water_movement_speed_multiplier, } } @@ -201,6 +203,445 @@ impl LivingEntity { world.get_block(&block_pos).await == &Block::POWDER_SNOW } + async fn get_effective_gravity(&self, caller: &Arc) -> f64 { + let final_gravity = caller.get_gravity(); + + if self.entity.velocity.load().y <= 0.0 + && self.has_effect(&StatusEffect::SLOW_FALLING).await + { + final_gravity.min(0.01) + } else { + final_gravity + } + } + + async fn tick_movement(&self, server: &Server, caller: Arc) { + if self.jumping_cooldown.load(Relaxed) != 0 { + self.jumping_cooldown.fetch_sub(1, Relaxed); + } + + let should_swim_in_fluids = if let Some(player) = caller.get_player() { + !player.is_flying().await + } else { + true + }; + + self.entity.check_zero_velo(); + + let mut movement_input = self.movement_input.load(); + + movement_input.x *= 0.98; + + movement_input.z *= 0.98; + + self.movement_input.store(movement_input); + + // TODO: Tick AI + + if self.jumping.load(SeqCst) && should_swim_in_fluids { + let in_lava = self.entity.touching_lava.load(SeqCst); + + let in_water = self.entity.touching_water.load(SeqCst); + + let fluid_height = if in_lava { + self.entity.lava_height.load() + } else { + self.entity.water_height.load() + }; + + let swim_height = self.get_swim_height(); + + let on_ground = self.entity.on_ground.load(SeqCst); + + if (in_water || in_lava) && (!on_ground || fluid_height > swim_height) { + // Swim upward + + let mut velo = self.entity.velocity.load(); + + velo.y += 0.04; + + self.entity.velocity.store(velo); + } else if (on_ground || in_water && fluid_height <= swim_height) + && self.jumping_cooldown.load(SeqCst) == 0 + { + self.jump().await; + + self.jumping_cooldown.store(10, SeqCst); + } + } else { + self.jumping_cooldown.store(0, SeqCst); + } + + if self.has_effect(&StatusEffect::SLOW_FALLING).await + || self.has_effect(&StatusEffect::LEVITATION).await + { + self.fall_distance.store(0.0); + } + + let touching_water = self.entity.touching_water.load(SeqCst); + + // Strider is the only entity that has canWalkOnFluid = false + + if (touching_water || self.entity.touching_lava.load(SeqCst)) + && should_swim_in_fluids + && self.entity.entity_type != &EntityType::STRIDER + { + self.travel_in_fluid(caller.clone(), touching_water).await; + } else { + // TODO: Gliding + + self.travel_in_air(caller.clone()).await; + } + + //self.entity.tick_block_underneath(&caller); + + let suffocating = self.entity.tick_block_collisions(&caller, server).await; + + if suffocating { + self.damage(1.0, DamageType::IN_WALL).await; + } + } + + async fn travel_in_air(&self, caller: Arc) { + // applyMovementInput + + let (speed, friction) = if self.entity.on_ground.load(SeqCst) { + // getVelocityAffectingPos + + let slipperiness = f64::from( + self.entity + .get_block_with_y_offset(0.500_001) + .await + .1 + .slipperiness, + ); + + let speed = + self.movement_speed.load() * 0.216 / (slipperiness * slipperiness * slipperiness); + + (speed, slipperiness * 0.91) + } else { + let speed = if let Some(player) = caller.get_player() { + player.get_off_ground_speed().await + } else { + // TODO: If the passenger is a player, ogs = movement_speed * 0.1 + + 0.02 + }; + + (speed, 0.91) + }; + + self.entity + .update_velocity_from_input(self.movement_input.load(), speed); + + self.apply_climbing_speed(); + + self.make_move(caller.clone()).await; + + let mut velo = self.entity.velocity.load(); + + // TODO: Add powdered snow + + if (self.entity.horizontal_collision.load(SeqCst) || self.jumping.load(SeqCst)) + && (self.climbing.load(Relaxed)) + { + velo.y = 0.2; + } + + let levitation = self.get_effect(&StatusEffect::LEVITATION).await; + + if let Some(lev) = levitation { + velo.y += (0.05 * f64::from(lev.amplifier + 1) - velo.y) * 0.2; + } else { + velo.y -= self.get_effective_gravity(&caller).await; + + // TODO: If world is not loaded: replace effective gravity with: + + // if below world's bottom y then -0.1, else 0.0 + } + + // If entity has no drag: store velo and return + + velo.x *= friction; + + velo.z *= friction; + + velo.y *= if caller.is_flutterer() { + friction + } else { + 0.98 + }; + + self.entity.velocity.store(velo); + } + + async fn travel_in_fluid(&self, caller: Arc, water: bool) { + let movement_input = self.movement_input.load(); + + let y0 = self.entity.pos.load().y; + + let falling = self.entity.velocity.load().y <= 0.0; + + let gravity = self.get_effective_gravity(&caller).await; + + if water { + let mut friction = if self.entity.sprinting.load(Relaxed) { + 0.9 + } else { + f64::from(self.water_movement_speed_multiplier) + }; + + let mut speed = 0.02; + + let mut water_movement_efficiency = 0.0; // TODO: Entity attribute + + if water_movement_efficiency > 0.0 { + if !self.entity.on_ground.load(SeqCst) { + water_movement_efficiency *= 0.5; + } + + friction += (0.546_000_06 - friction) * water_movement_efficiency; + + speed += (self.movement_speed.load() - speed) * water_movement_efficiency; + } + + if self.has_effect(&StatusEffect::DOLPHINS_GRACE).await { + friction = 0.96; + } + + self.entity + .update_velocity_from_input(movement_input, speed); + + self.make_move(caller).await; + + let mut velo = self.entity.velocity.load(); + + if self.entity.horizontal_collision.load(SeqCst) && self.climbing.load(Relaxed) { + velo.y = 0.2; + } + + velo = velo.multiply(friction, 0.8, friction); + + self.apply_fluid_moving_speed(&mut velo.y, gravity, falling); + + self.entity.velocity.store(velo); + } else { + self.entity.update_velocity_from_input(movement_input, 0.02); + + self.make_move(caller).await; + + let mut velo = self.entity.velocity.load(); + + if self.entity.lava_height.load() <= self.get_swim_height() { + velo.x *= 0.5; + + velo.z *= 0.5; + + velo.y *= 0.8; + + self.apply_fluid_moving_speed(&mut velo.y, gravity, falling); + } else { + velo = velo * 0.5; + } + + if gravity != 0.0 { + velo.y -= gravity / 4.0; // Negative gravity = buoyancy + } + + self.entity.velocity.store(velo); + } + + let mut velo = self.entity.velocity.load(); + + velo.y += 0.6 - self.entity.pos.load().y + y0; + + if self.entity.horizontal_collision.load(SeqCst) + && !self + .entity + .world + .read() + .await + .check_fluid_collision(self.entity.bounding_box.load().shift(velo)) + .await + { + velo.y = 0.3; + + self.entity.velocity.store(velo); + } + } + + fn apply_fluid_moving_speed(&self, dy: &mut f64, gravity: f64, falling: bool) { + if gravity != 0.0 && !self.entity.sprinting.load(Relaxed) { + if falling && (*dy - 0.005).abs() >= 0.003 && (*dy - gravity / 16.0).abs() < 0.003 { + *dy = -0.003; + } else { + *dy -= gravity / 16.0; + } + } + } + + async fn make_move(&self, caller: Arc) { + self.entity + .move_entity(caller, self.entity.velocity.load()) + .await; + + self.check_climbing(); + } + + fn check_climbing(&self) { + // If spectator: return false + + // TODO + // let mut pos = self.entity.block_pos.load(); + + // let world = self.entity.world.read().await; + + // let (block, state) = world.get_block_and_state(&pos).await; + + // let name = block.properties(state.id).map(|props| props.name()); + + // if let Some(name) = name { + // if name == "LadderLikeProperties" + // || name == "ScaffoldingLikeProperties" + // || name == "CaveVinesLikeProperties" + // || name == "CaveVinesPlantLikeProperties" + // { + // self.climbing.store(true, Relaxed); + + // self.climbing_pos.store(Some(pos)); + + // return; + // } + + // if name == "OakTrapdoorLikeProperties" { + // let trapdoor = OakTrapdoorLikeProperties::from_state_id(state.id, &block); + + // pos.0.y -= 1; + + // let (down_block, down_state) = world.get_block_and_state(&pos).await; + + // let is_ladder = down_block + // .properties(down_state.id) + // .is_some_and(|down_props| down_props.name() == "LadderLikeProperties"); + + // if is_ladder { + // let ladder = LadderLikeProperties::from_state_id(down_state.id, &down_block); + + // if trapdoor.r#facing == ladder.r#facing { + // self.climbing.store(true, Relaxed); + + // self.climbing_pos.store(Some(pos)); + + // return; + // } + // } + // } + // } + + self.climbing.store(false, Relaxed); + + if self.entity.on_ground.load(SeqCst) { + self.climbing_pos.store(None); + } + } + + fn apply_climbing_speed(&self) { + if self.climbing.load(Relaxed) { + self.fall_distance.store(0.0); + + let mut velo = self.entity.velocity.load(); + + let pos = 0.15; + + let neg = -0.15; + + if velo.x < neg { + velo.x = neg; + } else if velo.x > pos { + velo.x = pos; + } + + if velo.z < neg { + velo.z = neg; + } else if velo.z > pos { + velo.z = pos; + } + + velo.y = velo.y.max(neg); + + // TODO + // if velo.y < 0.0 + // && self.entity.entity_type == &EntityType::PLAYER + // && self.entity.sneaking.load(Relaxed) + // { + // let block = self + // .entity + // .world + // .read() + // .await + // .get_block(&self.entity.block_pos.load()) + // .await; + + // if let Some(props) = block.properties(block.default_state.id) { + // if props.name() == "ScaffoldingLikeProperties" { + // velo.y = 0.0; + // } + // } + // } + + self.entity.velocity.store(velo); + } + } + + pub fn get_swim_height(&self) -> f64 { + let eye_height = self.entity.standing_eye_height; + + if self.entity.entity_type == &EntityType::BREEZE { + f64::from(eye_height) + } else if eye_height < 0.4 { + 0.0 + } else { + 0.4 + } + } + + async fn jump(&self) { + let jump = self.get_jump_velocity(1.0).await; + + if jump <= 1.0e-5 { + return; + } + + let mut velo = self.entity.velocity.load(); + + velo.y = jump.max(velo.y); + + if self.entity.sprinting.load(Relaxed) { + let yaw = f64::from(self.entity.yaw.load()).to_radians(); + + velo.x -= yaw.sin() * 0.2; + + velo.y += yaw.cos() * 0.2; + } + + self.entity.velocity.store(velo); + + self.entity.velocity_dirty.store(true, SeqCst); + } + + async fn get_jump_velocity(&self, mut strength: f64) -> f64 { + strength *= 1.0; // TODO: Entity Attribute jump strength + + strength *= f64::from(self.entity.get_jump_velocity_multiplier().await); + + if let Some(effect) = self.get_effect(&StatusEffect::JUMP_BOOST).await { + strength += 0.1 * f64::from(effect.amplifier + 1); + } + + strength + } + pub async fn update_fall_distance( &self, height_difference: f64, @@ -352,19 +793,6 @@ impl LivingEntity { } } - async fn tick_move(&self, entity: &dyn EntityBase, server: &Server) { - let velo = self.entity.velocity.load(); - let pos = self.entity.pos.load(); - self.entity - .pos - .store(Vector3::new(pos.x + velo.x, pos.y + velo.y, pos.z + velo.z)); - let multiplier = f64::from(Entity::velocity_multiplier(pos)); - self.entity - .velocity - .store(velo.multiply(multiplier, 1.0, multiplier)); - Entity::check_block_collision(entity, server).await; - } - async fn tick_effects(&self) { let mut effects_to_remove = Vec::new(); @@ -497,9 +925,19 @@ impl EntityBase for LivingEntity { true } + fn get_gravity(&self) -> f64 { + const GRAVITY: f64 = 0.08; + GRAVITY + } + async fn tick(&self, caller: Arc, server: &Server) { self.entity.tick(caller.clone(), server).await; - self.tick_move(caller.as_ref(), server).await; + self.tick_movement(server, caller.clone()).await; + // TODO + if caller.get_player().is_none() { + self.entity.send_pos_rot().await; + self.entity.send_velocity().await; + } self.tick_effects().await; if self.hurt_cooldown.load(Relaxed) > 0 { self.hurt_cooldown.fetch_sub(1, Relaxed); diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index 50edfb39c..651650f8e 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -132,6 +132,10 @@ where fn get_living_entity(&self) -> Option<&LivingEntity> { Some(&self.get_mob_entity().living_entity) } + + fn get_gravity(&self) -> f64 { + self.get_mob_entity().living_entity.get_gravity() + } } #[allow(dead_code)] diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 7e93fdaa9..695e12586 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -139,6 +139,10 @@ pub trait EntityBase: Send + Sync { false } + fn is_flutterer(&self) -> bool { + false + } + async fn damage_with_context( &self, amount: f32, @@ -685,7 +689,8 @@ impl Entity { self.velocity.store(motion); } - fn _tick_block_underneath(_caller: &Arc) { + #[allow(dead_code)] + fn tick_block_underneath(_caller: &Arc) { // let world = self.world.read().await; // let (pos, block, state) = self.get_block_with_y_offset(0.2).await; @@ -1095,8 +1100,7 @@ impl Entity { // Entity.updateVelocity in yarn - #[allow(dead_code)] - fn _update_velocity_from_input(&self, movement_input: Vector3, speed: f64) { + fn update_velocity_from_input(&self, movement_input: Vector3, speed: f64) { let final_input = self.movement_input_to_velocity(movement_input, speed); self.velocity.store(self.velocity.load() + final_input); @@ -1149,7 +1153,7 @@ impl Entity { } #[allow(clippy::float_cmp)] - async fn _get_jump_velocity_multiplier(&self) -> f32 { + async fn get_jump_velocity_multiplier(&self) -> f32 { let world = self.world.read().await; let f = world @@ -1644,25 +1648,6 @@ impl Entity { self.invulnerable.load(Relaxed) || self.damage_immunities.contains(damage_type) } - fn velocity_multiplier(_pos: Vector3) -> f32 { - // let world = self.world.read().await; - // TODO: handle when player is outside world - // let block = world.get_block(&self.block_pos.load()).await; - // block.velocity_multiplier - 1.0 - // if velo_multiplier == 1.0 { - // const VELOCITY_OFFSET: f64 = 0.500001; // Vanilla - // let pos_with_y_offset = BlockPos(Vector3::new( - // pos.x.floor() as i32, - // (pos.y - VELOCITY_OFFSET).floor() as i32, - // pos.z.floor() as i32, - // )); - // let block = world.get_block(&pos_with_y_offset).await.unwrap(); - // block.velocity_multiplier - // } else { - // } - } - pub async fn check_block_collision(entity: &dyn EntityBase, server: &Server) { let aabb = entity.get_entity().bounding_box.load(); let blockpos = BlockPos::new( diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index ce5b22eb4..ed4d78034 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -604,6 +604,29 @@ impl Player { self.sleeping_since.store(Some(0)); } + pub async fn get_off_ground_speed(&self) -> f64 { + let sprinting = self.get_entity().sprinting.load(Ordering::Relaxed); + + if !self.get_entity().has_vehicle().await { + let fly_speed = { + let abilities = self.abilities.lock().await; + + abilities.flying.then_some(f64::from(abilities.fly_speed)) + }; + + if let Some(flying) = fly_speed { + return if sprinting { flying * 2.0 } else { flying }; + } + } + + if sprinting { 0.025_999_999 } else { 0.02 } + } + + pub async fn is_flying(&self) -> bool { + let abilities = self.abilities.lock().await; + abilities.flying + } + pub async fn wake_up(&self) { let world = self.world().await; let respawn_point = self diff --git a/pumpkin/src/net/java/mod.rs b/pumpkin/src/net/java/mod.rs index fecc230c4..d901edebf 100644 --- a/pumpkin/src/net/java/mod.rs +++ b/pumpkin/src/net/java/mod.rs @@ -8,7 +8,7 @@ use pumpkin_config::networking::compression::CompressionInfo; use pumpkin_protocol::java::server::play::{ SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand, SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport, - SCookieResponse as SPCookieResponse, SInteract, SKeepAlive, SPickItemFromBlock, + SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, SKeepAlive, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn, @@ -668,6 +668,9 @@ impl JavaClient { self.handle_chat_session_update(player, server, SPlayerSession::read(payload)?) .await; } + SCustomPayload::PACKET_ID => { + // TODO: this fixes Failed to handle player packet id for now + } _ => { log::warn!("Failed to handle player packet id {}", packet.id); }