From 49d63a39c0ef37d107f2a167bd6c68cbe60bd7cb Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 5 Aug 2022 12:36:34 -0700 Subject: [PATCH] Clean up client and fix names --- build/entity_event.rs | 4 +- build/main.rs | 6 +- examples/combat.rs | 277 ++++++++++++++------- examples/conway.rs | 193 +++++++++++---- examples/cow_sphere.rs | 149 +++++++++++- examples/raycast.rs | 144 ++++++++++- examples/terrain.rs | 144 ++++++++++- packet-inspector/src/main.rs | 10 +- src/client.rs | 439 ++++++++++++++-------------------- src/client/event.rs | 65 +++-- src/config.rs | 4 +- src/entity.rs | 44 ++-- src/entity/data.rs | 261 +------------------- src/entity/kinds.rs | 11 - src/entity/types.rs | 256 ++++++++++++++++++++ src/player_list.rs | 42 ++-- src/protocol_inner/packets.rs | 12 +- src/server.rs | 25 +- 18 files changed, 1312 insertions(+), 774 deletions(-) delete mode 100644 src/entity/kinds.rs create mode 100644 src/entity/types.rs diff --git a/build/entity_event.rs b/build/entity_event.rs index db59eb8..d4deae3 100644 --- a/build/entity_event.rs +++ b/build/entity_event.rs @@ -44,11 +44,11 @@ pub fn build() -> anyhow::Result { Ok(quote! { #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] - pub enum Event { + pub enum EntityEvent { #(#event_variants,)* } - impl Event { + impl EntityEvent { pub(crate) fn status_or_animation(self) -> StatusOrAnimation { match self { #(#status_arms)* diff --git a/build/main.rs b/build/main.rs index a2f5fd8..180b87c 100644 --- a/build/main.rs +++ b/build/main.rs @@ -16,11 +16,13 @@ pub fn main() -> anyhow::Result<()> { (block::build, "block.rs"), ]; + for (_, file_name) in generators { + println!("cargo:rerun-if-changed=extracted/{file_name}"); + } + let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?; for (g, file_name) in generators { - println!("cargo:rerun-if-changed=extracted/{file_name}"); - let path = Path::new(&out_dir).join(file_name); let code = g()?.to_string(); fs::write(&path, &code)?; diff --git a/examples/combat.rs b/examples/combat.rs index db2446b..bd8dd2a 100644 --- a/examples/combat.rs +++ b/examples/combat.rs @@ -3,15 +3,15 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use log::LevelFilter; use valence::block::{BlockPos, BlockState}; -use valence::client::{ClientId, Event, GameMode, Hand, InteractWithEntityKind}; +use valence::client::{Client, ClientEvent, ClientId, GameMode, Hand, InteractWithEntityKind}; use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; -use valence::entity::data::Pose; -use valence::entity::{TrackedData, EntityId, EntityKind, Event as EntityEvent}; +use valence::entity::types::Pose; +use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::{async_trait, Ticks}; -use vek::Vec3; +use vek::{Vec2, Vec3}; pub fn main() -> ShutdownResult { env_logger::Builder::new() @@ -119,7 +119,7 @@ impl Config for Game { let current_tick = server.shared.current_tick(); server.clients.retain(|client_id, client| { - if client.created_tick() == current_tick { + if client.created_this_tick() { if self .player_count .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { @@ -131,6 +131,23 @@ impl Config for Game { return false; } + let (player_id, player) = match server.entities.create_with_uuid( + EntityKind::Player, + client.uuid(), + EntityState::default(), + ) { + Some(e) => e, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + }; + + player.state.client = client_id; + + client.state.player = player_id; + client.state.extra_knockback = true; + client.spawn(world_id); client.set_game_mode(GameMode::Survival); client.teleport( @@ -152,17 +169,6 @@ impl Config for Game { None, ); - let (player_id, player) = server - .entities - .create_with_uuid(EntityKind::Player, client.uuid(), EntityState::default()) - .unwrap(); - - client.state.player = player_id; - client.state.extra_knockback = true; - - player.state.client = client_id; - player.state.last_attack_time = 0; - client.send_message("Welcome to the arena.".italic()); if self.player_count.load(Ordering::SeqCst) <= 1 { client.send_message("Have another player join the game with you.".italic()); @@ -176,41 +182,6 @@ impl Config for Game { return false; } - while let Some(event) = client.pop_event() { - match event { - Event::StartSprinting => { - client.state.extra_knockback = true; - } - Event::InteractWithEntity { - id, - kind: InteractWithEntityKind::Attack, - .. - } => { - if let Some(target) = server.entities.get_mut(id) { - if !target.state.attacked - && current_tick - target.state.last_attack_time >= 10 - && id != client.state.player - { - target.state.attacked = true; - target.state.attacker_pos = client.position(); - target.state.extra_knockback = client.state.extra_knockback; - target.state.last_attack_time = current_tick; - - client.state.extra_knockback = false; - } - } - } - Event::ArmSwing(hand) => { - let player = server.entities.get_mut(client.state.player).unwrap(); - match hand { - Hand::Main => player.trigger_event(EntityEvent::SwingMainHand), - Hand::Off => player.trigger_event(EntityEvent::SwingOffHand), - } - } - _ => (), - } - } - if client.position().y <= 0.0 { client.teleport( [ @@ -223,53 +194,189 @@ impl Config for Game { ); } - let player = server.entities.get_mut(client.state.player).unwrap(); + loop { + let player = server + .entities + .get_mut(client.state.player) + .expect("missing player entity"); - player.set_world(client.world()); - player.set_position(client.position()); - player.set_yaw(client.yaw()); - player.set_head_yaw(client.yaw()); - player.set_pitch(client.pitch()); - player.set_on_ground(client.on_ground()); + match client_event_boilerplate(client, player) { + Some(ClientEvent::StartSprinting) => { + client.state.extra_knockback = true; + } + Some(ClientEvent::InteractWithEntity { + id, + kind: InteractWithEntityKind::Attack, + .. + }) => { + if let Some(target) = server.entities.get_mut(id) { + if !target.state.attacked + && current_tick - target.state.last_attack_time >= 10 + && id != client.state.player + { + target.state.attacked = true; + target.state.attacker_pos = client.position(); + target.state.extra_knockback = client.state.extra_knockback; + target.state.last_attack_time = current_tick; - if let TrackedData::Player(player) = player.view_mut() { - if client.is_sneaking() { - player.set_pose(Pose::Sneaking); - } else { - player.set_pose(Pose::Standing); + client.state.extra_knockback = false; + } + } + } + Some(_) => {} + None => break, } - - player.set_sprinting(client.is_sprinting()); } true }); - for (_, e) in server.entities.iter_mut() { - if e.state.attacked { - e.state.attacked = false; - let victim = server.clients.get_mut(e.state.client).unwrap(); + for (_, entity) in server.entities.iter_mut() { + if entity.state.attacked { + entity.state.attacked = false; + if let Some(victim) = server.clients.get_mut(entity.state.client) { + let victim_pos = Vec2::new(victim.position().x, victim.position().z); + let attacker_pos = + Vec2::new(entity.state.attacker_pos.x, entity.state.attacker_pos.z); - let mut vel = (victim.position() - e.state.attacker_pos).normalized(); + let dir = (victim_pos - attacker_pos).normalized(); - let knockback_xz = if e.state.extra_knockback { 18.0 } else { 8.0 }; - let knockback_y = if e.state.extra_knockback { - 8.432 - } else { - 6.432 - }; + let knockback_xz = if entity.state.extra_knockback { + 18.0 + } else { + 8.0 + }; + let knockback_y = if entity.state.extra_knockback { + 8.432 + } else { + 6.432 + }; - vel.x *= knockback_xz; - vel.y = knockback_y; - vel.z *= knockback_xz; + let vel = Vec3::new(dir.x * knockback_xz, knockback_y, dir.y * knockback_xz); + victim.set_velocity(vel.as_()); - victim.set_velocity(victim.velocity() / 2.0 + vel.as_()); - - e.trigger_event(EntityEvent::DamageFromGenericSource); - e.trigger_event(EntityEvent::Damage); - victim.trigger_entity_event(EntityEvent::DamageFromGenericSource); - victim.trigger_entity_event(EntityEvent::Damage); + entity.push_event(EntityEvent::DamageFromGenericSource); + entity.push_event(EntityEvent::Damage); + victim.push_entity_event(EntityEvent::DamageFromGenericSource); + victim.push_entity_event(EntityEvent::Damage); + } } } } } + +fn client_event_boilerplate( + client: &mut Client, + entity: &mut Entity, +) -> Option { + let event = client.pop_event()?; + + match &event { + ClientEvent::ChatMessage { .. } => {} + ClientEvent::SettingsChanged { + view_distance, + main_hand, + displayed_skin_parts, + .. + } => { + client.set_view_distance(*view_distance); + + let player = client.player_mut(); + + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + + if let TrackedData::Player(player) = entity.data_mut() { + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + } + } + ClientEvent::MovePosition { + position, + on_ground, + } => { + entity.set_position(*position); + entity.set_on_ground(*on_ground); + } + ClientEvent::MovePositionAndRotation { + position, + yaw, + pitch, + on_ground, + } => { + entity.set_position(*position); + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveRotation { + yaw, + pitch, + on_ground, + } => { + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveOnGround { on_ground } => { + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveVehicle { .. } => {} + ClientEvent::StartSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Standing { + player.set_pose(Pose::Sneaking); + } + } + } + ClientEvent::StopSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Sneaking { + player.set_pose(Pose::Standing); + } + } + } + ClientEvent::StartSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(true); + } + } + ClientEvent::StopSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(false); + } + } + ClientEvent::StartJumpWithHorse { .. } => {} + ClientEvent::StopJumpWithHorse => {} + ClientEvent::LeaveBed => {} + ClientEvent::OpenHorseInventory => {} + ClientEvent::StartFlyingWithElytra => {} + ClientEvent::ArmSwing(hand) => { + entity.push_event(match hand { + Hand::Main => EntityEvent::SwingMainHand, + Hand::Off => EntityEvent::SwingOffHand, + }); + } + ClientEvent::InteractWithEntity { .. } => {} + ClientEvent::SteerBoat { .. } => {} + ClientEvent::Digging { .. } => {} + } + + entity.set_world(client.world()); + + Some(event) +} diff --git a/examples/conway.rs b/examples/conway.rs index 86eb473..dffeb8f 100644 --- a/examples/conway.rs +++ b/examples/conway.rs @@ -7,11 +7,11 @@ use num::Integer; use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use valence::biome::Biome; use valence::block::BlockState; -use valence::client::{Event, Hand}; +use valence::client::{Client, ClientEvent, Hand}; use valence::config::{Config, ServerListPing}; use valence::dimension::{Dimension, DimensionId}; -use valence::entity::data::Pose; -use valence::entity::{TrackedData, EntityId, EntityKind, Event as EntityEvent}; +use valence::entity::types::Pose; +use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::{async_trait, ident}; @@ -42,12 +42,6 @@ struct ServerState { board_buf: Box<[bool]>, } -#[derive(Default)] -struct ClientState { - /// The client's player entity. - player: EntityId, -} - const MAX_PLAYERS: usize = 10; const SIZE_X: usize = 100; @@ -57,7 +51,7 @@ const BOARD_Y: i32 = 50; #[async_trait] impl Config for Game { type ChunkState = (); - type ClientState = ClientState; + type ClientState = EntityId; type EntityState = (); type ServerState = ServerState; type WorldState = (); @@ -121,7 +115,7 @@ impl Config for Game { ]; server.clients.retain(|_, client| { - if client.created_tick() == server.shared.current_tick() { + if client.created_this_tick() { if self .player_count .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { @@ -133,6 +127,17 @@ impl Config for Game { return false; } + match server + .entities + .create_with_uuid(EntityKind::Player, client.uuid(), ()) + { + Some((id, _)) => client.state = id, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + } + client.spawn(world_id); client.teleport(spawn_pos, 0.0, 0.0); @@ -145,65 +150,35 @@ impl Config for Game { None, ); - client.state.player = server - .entities - .create_with_uuid(EntityKind::Player, client.uuid(), ()) - .unwrap() - .0; - client.send_message("Welcome to Conway's game of life in Minecraft!".italic()); client.send_message("Hold the left mouse button to bring blocks to life.".italic()); } if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); - server.entities.delete(client.state.player); + server.entities.delete(client.state); world.meta.player_list_mut().remove(client.uuid()); return false; } - let player = server.entities.get_mut(client.state.player).unwrap(); + let player = server.entities.get_mut(client.state).unwrap(); if client.position().y <= 0.0 { client.teleport(spawn_pos, client.yaw(), client.pitch()); } - while let Some(event) = client.pop_event() { - match event { - Event::Digging { position, .. } => { - if (0..SIZE_X as i32).contains(&position.x) - && (0..SIZE_Z as i32).contains(&position.z) - && position.y == BOARD_Y - { - server.state.board - [position.x as usize + position.z as usize * SIZE_X] = true; - } + while let Some(event) = client_event_boilerplate(client, player) { + if let ClientEvent::Digging { position, .. } = event { + if (0..SIZE_X as i32).contains(&position.x) + && (0..SIZE_Z as i32).contains(&position.z) + && position.y == BOARD_Y + { + server.state.board[position.x as usize + position.z as usize * SIZE_X] = + true; } - Event::ArmSwing(hand) => match hand { - Hand::Main => player.trigger_event(EntityEvent::SwingMainHand), - Hand::Off => player.trigger_event(EntityEvent::SwingOffHand), - }, - _ => {} } } - player.set_world(client.world()); - player.set_position(client.position()); - player.set_yaw(client.yaw()); - player.set_head_yaw(client.yaw()); - player.set_pitch(client.pitch()); - player.set_on_ground(client.on_ground()); - - if let TrackedData::Player(player) = player.view_mut() { - if client.is_sneaking() { - player.set_pose(Pose::Sneaking); - } else { - player.set_pose(Pose::Standing); - } - - player.set_sprinting(client.is_sprinting()); - } - true }); @@ -269,3 +244,119 @@ impl Config for Game { } } } + +fn client_event_boilerplate( + client: &mut Client, + entity: &mut Entity, +) -> Option { + let event = client.pop_event()?; + + match &event { + ClientEvent::ChatMessage { .. } => {} + ClientEvent::SettingsChanged { + view_distance, + main_hand, + displayed_skin_parts, + .. + } => { + client.set_view_distance(*view_distance); + + let player = client.player_mut(); + + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + + if let TrackedData::Player(player) = entity.data_mut() { + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + } + } + ClientEvent::MovePosition { + position, + on_ground, + } => { + entity.set_position(*position); + entity.set_on_ground(*on_ground); + } + ClientEvent::MovePositionAndRotation { + position, + yaw, + pitch, + on_ground, + } => { + entity.set_position(*position); + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveRotation { + yaw, + pitch, + on_ground, + } => { + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveOnGround { on_ground } => { + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveVehicle { .. } => {} + ClientEvent::StartSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Standing { + player.set_pose(Pose::Sneaking); + } + } + } + ClientEvent::StopSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Sneaking { + player.set_pose(Pose::Standing); + } + } + } + ClientEvent::StartSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(true); + } + } + ClientEvent::StopSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(false); + } + } + ClientEvent::StartJumpWithHorse { .. } => {} + ClientEvent::StopJumpWithHorse => {} + ClientEvent::LeaveBed => {} + ClientEvent::OpenHorseInventory => {} + ClientEvent::StartFlyingWithElytra => {} + ClientEvent::ArmSwing(hand) => { + entity.push_event(match hand { + Hand::Main => EntityEvent::SwingMainHand, + Hand::Off => EntityEvent::SwingOffHand, + }); + } + ClientEvent::InteractWithEntity { .. } => {} + ClientEvent::SteerBoat { .. } => {} + ClientEvent::Digging { .. } => {} + } + + entity.set_world(client.world()); + + Some(event) +} diff --git a/examples/cow_sphere.rs b/examples/cow_sphere.rs index 7a9952d..69e5489 100644 --- a/examples/cow_sphere.rs +++ b/examples/cow_sphere.rs @@ -5,10 +5,11 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use log::LevelFilter; use valence::async_trait; use valence::block::{BlockPos, BlockState}; -use valence::client::GameMode; +use valence::client::{Client, ClientEvent, GameMode, Hand}; use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; -use valence::entity::{EntityId, EntityKind}; +use valence::entity::types::Pose; +use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::util::to_yaw_and_pitch; @@ -43,7 +44,7 @@ const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -25); #[async_trait] impl Config for Game { type ChunkState = (); - type ClientState = (); + type ClientState = EntityId; type EntityState = (); type ServerState = ServerState; type WorldState = (); @@ -92,10 +93,10 @@ impl Config for Game { } fn update(&self, server: &mut Server) { - let (world_id, world) = server.worlds.iter_mut().next().unwrap(); + let (world_id, world) = server.worlds.iter_mut().next().expect("missing world"); server.clients.retain(|_, client| { - if client.created_tick() == server.shared.current_tick() { + if client.created_this_tick() { if self .player_count .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { @@ -107,6 +108,17 @@ impl Config for Game { return false; } + match server + .entities + .create_with_uuid(EntityKind::Player, client.uuid(), ()) + { + Some((id, _)) => client.state = id, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + } + client.spawn(world_id); client.set_game_mode(GameMode::Creative); client.teleport( @@ -132,9 +144,18 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); world.meta.player_list_mut().remove(client.uuid()); + server.entities.delete(client.state); + return false; } + let entity = server + .entities + .get_mut(client.state) + .expect("missing player entity"); + + while client_event_boilerplate(client, entity).is_some() {} + true }); @@ -153,7 +174,7 @@ impl Config for Game { .map(|c| c.1.position()) .unwrap_or_default(); - // TODO: hardcoded eye pos. + // TODO: remove hardcoded eye pos. let eye_pos = Vec3::new(player_pos.x, player_pos.y + 1.6, player_pos.z); for (cow_id, p) in server @@ -194,3 +215,119 @@ fn fibonacci_spiral(n: usize) -> impl Iterator> { Vec3::new(theta.cos() * phi.sin(), theta.sin() * phi.sin(), phi.cos()) }) } + +fn client_event_boilerplate( + client: &mut Client, + entity: &mut Entity, +) -> Option { + let event = client.pop_event()?; + + match &event { + ClientEvent::ChatMessage { .. } => {} + ClientEvent::SettingsChanged { + view_distance, + main_hand, + displayed_skin_parts, + .. + } => { + client.set_view_distance(*view_distance); + + let player = client.player_mut(); + + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + + if let TrackedData::Player(player) = entity.data_mut() { + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + } + } + ClientEvent::MovePosition { + position, + on_ground, + } => { + entity.set_position(*position); + entity.set_on_ground(*on_ground); + } + ClientEvent::MovePositionAndRotation { + position, + yaw, + pitch, + on_ground, + } => { + entity.set_position(*position); + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveRotation { + yaw, + pitch, + on_ground, + } => { + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveOnGround { on_ground } => { + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveVehicle { .. } => {} + ClientEvent::StartSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Standing { + player.set_pose(Pose::Sneaking); + } + } + } + ClientEvent::StopSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Sneaking { + player.set_pose(Pose::Standing); + } + } + } + ClientEvent::StartSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(true); + } + } + ClientEvent::StopSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(false); + } + } + ClientEvent::StartJumpWithHorse { .. } => {} + ClientEvent::StopJumpWithHorse => {} + ClientEvent::LeaveBed => {} + ClientEvent::OpenHorseInventory => {} + ClientEvent::StartFlyingWithElytra => {} + ClientEvent::ArmSwing(hand) => { + entity.push_event(match hand { + Hand::Main => EntityEvent::SwingMainHand, + Hand::Off => EntityEvent::SwingOffHand, + }); + } + ClientEvent::InteractWithEntity { .. } => {} + ClientEvent::SteerBoat { .. } => {} + ClientEvent::Digging { .. } => {} + } + + entity.set_world(client.world()); + + Some(event) +} diff --git a/examples/raycast.rs b/examples/raycast.rs index 194bb26..021579f 100644 --- a/examples/raycast.rs +++ b/examples/raycast.rs @@ -4,10 +4,11 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use log::LevelFilter; use valence::async_trait; use valence::block::{BlockPos, BlockState}; -use valence::client::GameMode; +use valence::client::{Client, ClientEvent, GameMode, Hand}; use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; -use valence::entity::{TrackedData, EntityKind}; +use valence::entity::types::Pose; +use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::spatial_index::RaycastHit; use valence::text::{Color, TextFormat}; @@ -41,7 +42,7 @@ const PLAYER_EYE_HEIGHT: f64 = 1.6; #[async_trait] impl Config for Game { type ChunkState = (); - type ClientState = (); + type ClientState = EntityId; /// `true` for entities that have been intersected with. type EntityState = bool; type ServerState = (); @@ -99,7 +100,7 @@ impl Config for Game { let (world_id, world) = server.worlds.iter_mut().next().unwrap(); server.clients.retain(|_, client| { - if client.created_tick() == server.shared.current_tick() { + if client.created_this_tick() { if self .player_count .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { @@ -111,6 +112,17 @@ impl Config for Game { return false; } + match server + .entities + .create_with_uuid(EntityKind::Player, client.uuid(), false) + { + Some((id, _)) => client.state = id, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + } + client.spawn(world_id); client.set_game_mode(GameMode::Creative); client.teleport( @@ -142,6 +154,8 @@ impl Config for Game { if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); world.meta.player_list_mut().remove(client.uuid()); + server.entities.delete(client.state); + return false; } @@ -162,12 +176,16 @@ impl Config for Game { } } + while client_event_boilerplate(client, server.entities.get_mut(client.state).unwrap()) + .is_some() + {} + true }); for (_, e) in server.entities.iter_mut() { let intersected = e.state; - if let TrackedData::Sheep(sheep) = &mut e.view_mut() { + if let TrackedData::Sheep(sheep) = &mut e.data_mut() { if intersected { sheep.set_color(5); } else { @@ -178,3 +196,119 @@ impl Config for Game { } } } + +fn client_event_boilerplate( + client: &mut Client, + entity: &mut Entity, +) -> Option { + let event = client.pop_event()?; + + match &event { + ClientEvent::ChatMessage { .. } => {} + ClientEvent::SettingsChanged { + view_distance, + main_hand, + displayed_skin_parts, + .. + } => { + client.set_view_distance(*view_distance); + + let player = client.player_mut(); + + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + + if let TrackedData::Player(player) = entity.data_mut() { + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + } + } + ClientEvent::MovePosition { + position, + on_ground, + } => { + entity.set_position(*position); + entity.set_on_ground(*on_ground); + } + ClientEvent::MovePositionAndRotation { + position, + yaw, + pitch, + on_ground, + } => { + entity.set_position(*position); + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveRotation { + yaw, + pitch, + on_ground, + } => { + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveOnGround { on_ground } => { + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveVehicle { .. } => {} + ClientEvent::StartSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Standing { + player.set_pose(Pose::Sneaking); + } + } + } + ClientEvent::StopSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Sneaking { + player.set_pose(Pose::Standing); + } + } + } + ClientEvent::StartSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(true); + } + } + ClientEvent::StopSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(false); + } + } + ClientEvent::StartJumpWithHorse { .. } => {} + ClientEvent::StopJumpWithHorse => {} + ClientEvent::LeaveBed => {} + ClientEvent::OpenHorseInventory => {} + ClientEvent::StartFlyingWithElytra => {} + ClientEvent::ArmSwing(hand) => { + entity.push_event(match hand { + Hand::Main => EntityEvent::SwingMainHand, + Hand::Off => EntityEvent::SwingOffHand, + }); + } + ClientEvent::InteractWithEntity { .. } => {} + ClientEvent::SteerBoat { .. } => {} + ClientEvent::Digging { .. } => {} + } + + entity.set_world(client.world()); + + Some(event) +} diff --git a/examples/terrain.rs b/examples/terrain.rs index 015f17f..f689005 100644 --- a/examples/terrain.rs +++ b/examples/terrain.rs @@ -8,9 +8,11 @@ use rayon::iter::ParallelIterator; use valence::async_trait; use valence::block::{BlockState, PropName, PropValue}; use valence::chunk::ChunkPos; -use valence::client::GameMode; +use valence::client::{Client, ClientEvent, GameMode, Hand}; use valence::config::{Config, ServerListPing}; use valence::dimension::DimensionId; +use valence::entity::types::Pose; +use valence::entity::{Entity, EntityEvent, EntityId, EntityKind, TrackedData}; use valence::server::{Server, SharedServer, ShutdownResult}; use valence::text::{Color, TextFormat}; use valence::util::chunks_in_view_distance; @@ -51,7 +53,7 @@ const MAX_PLAYERS: usize = 10; #[async_trait] impl Config for Game { type ChunkState = (); - type ClientState = (); + type ClientState = EntityId; type EntityState = (); type ServerState = (); type WorldState = (); @@ -90,7 +92,7 @@ impl Config for Game { let mut chunks_to_unload = HashSet::<_>::from_iter(world.chunks.iter().map(|t| t.0)); server.clients.retain(|_, client| { - if client.created_tick() == server.shared.current_tick() { + if client.created_this_tick() { if self .player_count .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { @@ -102,9 +104,19 @@ impl Config for Game { return false; } + match server + .entities + .create_with_uuid(EntityKind::Player, client.uuid(), ()) + { + Some((id, _)) => client.state = id, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + } + client.spawn(world_id); client.set_game_mode(GameMode::Creative); - client.set_max_view_distance(32); client.teleport([0.0, 200.0, 0.0], 0.0, 0.0); world.meta.player_list_mut().insert( @@ -117,16 +129,20 @@ impl Config for Game { ); client.send_message("Welcome to the terrain example!".italic()); - client - .send_message("Explore this infinite procedurally generated terrain.".italic()); } if client.is_disconnected() { self.player_count.fetch_sub(1, Ordering::SeqCst); world.meta.player_list_mut().remove(client.uuid()); + server.entities.delete(client.state); + return false; } + if let Some(entity) = server.entities.get_mut(client.state) { + while client_event_boilerplate(client, entity).is_some() {} + } + let dist = client.view_distance(); let p = client.position(); @@ -321,3 +337,119 @@ fn fbm(noise: &SuperSimplex, p: [f64; 3], octaves: u32, lacunarity: f64, persist fn noise01(noise: &SuperSimplex, xyz: [f64; 3]) -> f64 { (noise.get(xyz) + 1.0) / 2.0 } + +fn client_event_boilerplate( + client: &mut Client, + entity: &mut Entity, +) -> Option { + let event = client.pop_event()?; + + match &event { + ClientEvent::ChatMessage { .. } => {} + ClientEvent::SettingsChanged { + view_distance, + main_hand, + displayed_skin_parts, + .. + } => { + client.set_view_distance(*view_distance); + + let player = client.player_mut(); + + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + + if let TrackedData::Player(player) = entity.data_mut() { + player.set_cape(displayed_skin_parts.cape()); + player.set_jacket(displayed_skin_parts.jacket()); + player.set_left_sleeve(displayed_skin_parts.left_sleeve()); + player.set_right_sleeve(displayed_skin_parts.right_sleeve()); + player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); + player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); + player.set_hat(displayed_skin_parts.hat()); + player.set_main_arm(*main_hand as u8); + } + } + ClientEvent::MovePosition { + position, + on_ground, + } => { + entity.set_position(*position); + entity.set_on_ground(*on_ground); + } + ClientEvent::MovePositionAndRotation { + position, + yaw, + pitch, + on_ground, + } => { + entity.set_position(*position); + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveRotation { + yaw, + pitch, + on_ground, + } => { + entity.set_yaw(*yaw); + entity.set_head_yaw(*yaw); + entity.set_pitch(*pitch); + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveOnGround { on_ground } => { + entity.set_on_ground(*on_ground); + } + ClientEvent::MoveVehicle { .. } => {} + ClientEvent::StartSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Standing { + player.set_pose(Pose::Sneaking); + } + } + } + ClientEvent::StopSneaking => { + if let TrackedData::Player(player) = entity.data_mut() { + if player.get_pose() == Pose::Sneaking { + player.set_pose(Pose::Standing); + } + } + } + ClientEvent::StartSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(true); + } + } + ClientEvent::StopSprinting => { + if let TrackedData::Player(player) = entity.data_mut() { + player.set_sprinting(false); + } + } + ClientEvent::StartJumpWithHorse { .. } => {} + ClientEvent::StopJumpWithHorse => {} + ClientEvent::LeaveBed => {} + ClientEvent::OpenHorseInventory => {} + ClientEvent::StartFlyingWithElytra => {} + ClientEvent::ArmSwing(hand) => { + entity.push_event(match hand { + Hand::Main => EntityEvent::SwingMainHand, + Hand::Off => EntityEvent::SwingOffHand, + }); + } + ClientEvent::InteractWithEntity { .. } => {} + ClientEvent::SteerBoat { .. } => {} + ClientEvent::Digging { .. } => {} + } + + entity.set_world(client.world()); + + Some(event) +} diff --git a/packet-inspector/src/main.rs b/packet-inspector/src/main.rs index fd6da54..b8bbfc6 100644 --- a/packet-inspector/src/main.rs +++ b/packet-inspector/src/main.rs @@ -13,13 +13,13 @@ use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::Semaphore; use valence::protocol::codec::Decoder; -use valence::protocol::packets::handshake::{Handshake, HandshakeNextState}; -use valence::protocol::packets::login::c2s::{EncryptionResponse, LoginStart}; -use valence::protocol::packets::login::s2c::{LoginSuccess, S2cLoginPacket}; +use valence::protocol::packets::c2s::handshake::{Handshake, HandshakeNextState}; +use valence::protocol::packets::c2s::login::{EncryptionResponse, LoginStart}; use valence::protocol::packets::c2s::play::C2sPlayPacket; +use valence::protocol::packets::c2s::status::{QueryPing, QueryRequest}; +use valence::protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket}; use valence::protocol::packets::s2c::play::S2cPlayPacket; -use valence::protocol::packets::status::c2s::{QueryPing, QueryRequest}; -use valence::protocol::packets::status::s2c::{QueryPong, QueryResponse}; +use valence::protocol::packets::s2c::status::{QueryPong, QueryResponse}; use valence::protocol::packets::{DecodePacket, EncodePacket}; use valence::protocol::{Encode, VarInt}; diff --git a/src/client.rs b/src/client.rs index ab7ac98..e76617c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,7 +4,7 @@ use std::collections::{HashSet, VecDeque}; use std::iter::FusedIterator; use std::time::Duration; -use bitfield_struct::bitfield; +pub use bitfield_struct::bitfield; pub use event::*; use flume::{Receiver, Sender, TrySendError}; use rayon::iter::ParallelIterator; @@ -16,10 +16,9 @@ use crate::block_pos::BlockPos; use crate::chunk_pos::ChunkPos; use crate::config::Config; use crate::dimension::DimensionId; -use crate::entity::kinds::Player; +use crate::entity::data::Player; use crate::entity::{ - velocity_to_packet_units, Entities, EntityId, EntityKind, Event as EntityEvent, - StatusOrAnimation, + velocity_to_packet_units, Entities, EntityEvent, EntityId, EntityKind, StatusOrAnimation, }; use crate::player_textures::SignedPlayerTextures; use crate::protocol_inner::packets::c2s::play::{ @@ -42,9 +41,9 @@ use crate::slotmap::{Key, SlotMap}; use crate::text::Text; use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance}; use crate::world::{WorldId, Worlds}; -use crate::{ident, Ticks, LIBRARY_NAMESPACE, STANDARD_TPS}; +use crate::{ident, LIBRARY_NAMESPACE}; -/// Contains the [`Event`] enum and related data types. +/// Contains the [`ClientEvent`] enum and related data types. mod event; /// A container for all [`Client`]s on a [`Server`](crate::server::Server). @@ -62,8 +61,8 @@ impl Clients { } pub(crate) fn insert(&mut self, client: Client) -> (ClientId, &mut Client) { - let (id, client) = self.sm.insert(client); - (ClientId(id), client) + let (k, client) = self.sm.insert(client); + (ClientId(k), client) } /// Removes a client from the server. @@ -74,7 +73,7 @@ impl Clients { self.sm.remove(client.0).is_some() } - /// Deletes all clients from the server (as if by [`Self::delete`]) for + /// Deletes all clients from the server for /// which `f` returns `true`. /// /// All clients are visited in an unspecified order. @@ -146,7 +145,7 @@ impl ClientId { /// Represents a remote connection to a client after successfully logging in. /// -/// Much like an [`Entity`], clients posess a location, rotation, and UUID. +/// Much like an [`Entity`], clients possess a location, rotation, and UUID. /// However, clients are handled separately from entities and are partially /// managed by the library. /// @@ -154,30 +153,28 @@ impl ClientId { /// cannot break blocks, hurt entities, or see other clients. Interactions with /// the server must be handled explicitly with [`Self::pop_event`]. /// -/// Additionally, clients posess [`Player`] entity data which is only visible to -/// themselves. This can be accessed with [`Self::player`] and +/// Additionally, clients possess [`Player`] entity data which is only visible +/// to themselves. This can be accessed with [`Self::player`] and /// [`Self::player_mut`]. /// /// # The Difference Between a "Client" and a "Player" /// /// Normally in Minecraft, players and clients are one and the same. Players are -/// simply a special type of entity which is backed by a remote connection. +/// simply a subtype of the entity base class backed by a remote connection. /// -/// In Valence however, clients and players have been decoupled. This separation -/// was done primarily to enable multithreaded client updates. +/// In Valence however, clients and players are decoupled. This separation +/// allows for greater flexibility and parallelism. pub struct Client { /// Custom state. pub state: C::ClientState, /// Setting this to `None` disconnects the client. send: SendOpt, recv: Receiver, - /// The tick this client was created. - created_tick: Ticks, uuid: Uuid, username: String, textures: Option, world: WorldId, - new_position: Vec3, + position: Vec3, old_position: Vec3, /// Measured in m/s. velocity: Vec3, @@ -185,6 +182,7 @@ pub struct Client { yaw: f32, /// Measured in degrees pitch: f32, + view_distance: u8, /// Counts up as teleports are made. teleport_id_counter: u32, /// The number of pending client teleports that have yet to receive a @@ -194,11 +192,9 @@ pub struct Client { spawn_position: BlockPos, spawn_position_yaw: f32, death_location: Option<(DimensionId, BlockPos)>, - events: VecDeque, + events: VecDeque, /// The ID of the last keepalive sent. last_keepalive_id: i64, - new_max_view_distance: u8, - old_max_view_distance: u8, /// Entities that were visible to this client at the end of the last tick. /// This is used to determine what entity create/destroy packets should be /// sent. @@ -212,21 +208,15 @@ pub struct Client { msgs_to_send: Vec, attack_speed: f64, movement_speed: f64, - flags: ClientFlags, + bits: ClientBits, /// The data for the client's own player entity. player_data: Player, entity_events: Vec, } #[bitfield(u16)] -pub(crate) struct ClientFlags { +struct ClientBits { spawn: bool, - sneaking: bool, - sprinting: bool, - jumping_with_horse: bool, - on_ground: bool, - /// If any of position, yaw, or pitch were modified by the - /// user this tick. teleported_this_tick: bool, /// If spawn_position or spawn_position_yaw were modified this tick. modified_spawn_position: bool, @@ -236,33 +226,34 @@ pub(crate) struct ClientFlags { attack_speed_modified: bool, movement_speed_modified: bool, velocity_modified: bool, - #[bits(4)] + created_this_tick: bool, + view_distance_modified: bool, + #[bits(6)] _pad: u8, } impl Client { pub(crate) fn new( packet_channels: C2sPacketChannels, - server: &SharedServer, ncd: NewClientData, - data: C::ClientState, + state: C::ClientState, ) -> Self { let (send, recv) = packet_channels; Self { - state: data, + state, send: Some(send), recv, - created_tick: server.current_tick(), uuid: ncd.uuid, username: ncd.username, textures: ncd.textures, world: WorldId::default(), - new_position: Vec3::default(), + position: Vec3::default(), old_position: Vec3::default(), velocity: Vec3::default(), yaw: 0.0, pitch: 0.0, + view_distance: 8, teleport_id_counter: 0, pending_teleports: 0, spawn_position: BlockPos::default(), @@ -270,8 +261,6 @@ impl Client { death_location: None, events: VecDeque::new(), last_keepalive_id: 0, - new_max_view_distance: 16, - old_max_view_distance: 0, loaded_entities: HashSet::new(), loaded_chunks: HashSet::new(), new_game_mode: GameMode::Survival, @@ -281,17 +270,18 @@ impl Client { msgs_to_send: Vec::new(), attack_speed: 4.0, movement_speed: 0.7, - flags: ClientFlags::new() + bits: ClientBits::new() .with_modified_spawn_position(true) - .with_got_keepalive(true), + .with_got_keepalive(true) + .with_created_this_tick(true), player_data: Player::new(), entity_events: Vec::new(), } } - /// Gets the tick that this client was created. - pub fn created_tick(&self) -> Ticks { - self.created_tick + /// If the client joined the game this tick. + pub fn created_this_tick(&self) -> bool { + self.bits.created_this_tick() } /// Gets the client's UUID. @@ -304,16 +294,6 @@ impl Client { &self.username } - /// Returns the sneaking state of this client. - pub fn is_sneaking(&self) -> bool { - self.flags.sneaking() - } - - /// Returns the sprinting state of this client. - pub fn is_sprinting(&self) -> bool { - self.flags.sprinting() - } - /// Gets the player textures of this client. If the client does not have /// a skin, then `None` is returned. pub fn textures(&self) -> Option<&SignedPlayerTextures> { @@ -325,13 +305,14 @@ impl Client { self.world } - /// Changes the world this client is located in. + /// Changes the world this client is located in and respawns the client. + /// This can be used to respawn the client after death. /// /// The given [`WorldId`] must be valid. Otherwise, the client is /// disconnected. pub fn spawn(&mut self, world: WorldId) { self.world = world; - self.flags.set_spawn(true); + self.bits.set_spawn(true); } /// Sends a system message to the player which is visible in the chat. @@ -344,7 +325,7 @@ impl Client { /// Gets the absolute position of this client in the world it is located /// in. pub fn position(&self) -> Vec3 { - self.new_position + self.position } /// Changes the position and rotation of this client in the world it is @@ -352,31 +333,14 @@ impl Client { /// /// If you want to change the client's world, use [`Self::spawn`]. pub fn teleport(&mut self, pos: impl Into>, yaw: f32, pitch: f32) { - self.new_position = pos.into(); + self.position = pos.into(); self.yaw = yaw; self.pitch = pitch; - self.velocity = Vec3::default(); - if !self.flags.teleported_this_tick() { - self.flags.set_teleported_this_tick(true); - - self.pending_teleports = match self.pending_teleports.checked_add(1) { - Some(n) => n, - None => { - log::warn!("too many pending teleports for {}", self.username()); - self.disconnect_no_reason(); - return; - } - }; - - self.teleport_id_counter = self.teleport_id_counter.wrapping_add(1); - } + self.bits.set_teleported_this_tick(true); } - /// Gets the velocity of this client in m/s. - /// - /// The velocity of a client is derived from their current and previous - /// position. + /// Gets the most recently set velocity of this client in m/s. pub fn velocity(&self) -> Vec3 { self.velocity } @@ -384,7 +348,7 @@ impl Client { /// Sets the client's velocity in m/s. pub fn set_velocity(&mut self, velocity: impl Into>) { self.velocity = velocity.into(); - self.flags.set_velocity_modified(true); + self.bits.set_velocity_modified(true); } /// Gets this client's yaw. @@ -410,12 +374,13 @@ impl Client { if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw { self.spawn_position = pos; self.spawn_position_yaw = yaw_degrees; - self.flags.set_modified_spawn_position(true); + self.bits.set_modified_spawn_position(true); } } /// Gets the last death location of this client. The client will see /// `minecraft:recovery_compass` items point at the returned position. + /// /// If the client's current dimension differs from the returned /// dimension or the location is `None` then the compass will spin /// randomly. @@ -482,7 +447,7 @@ impl Client { pub fn set_attack_speed(&mut self, speed: f64) { if self.attack_speed != speed { self.attack_speed = speed; - self.flags.set_attack_speed_modified(true); + self.bits.set_attack_speed_modified(true); } } @@ -495,7 +460,7 @@ impl Client { pub fn set_movement_speed(&mut self, speed: f64) { if self.movement_speed != speed { self.movement_speed = speed; - self.flags.set_movement_speed_modified(true); + self.bits.set_movement_speed_modified(true); } } @@ -504,11 +469,6 @@ impl Client { self.send_packet(ClearTitles { reset: true }); } - /// Gets if the client is on the ground, as determined by the client. - pub fn on_ground(&self) -> bool { - self.flags.on_ground() - } - /// Gets whether or not the client is connected to the server. /// /// A disconnected client object will never become reconnected. It is your @@ -518,42 +478,46 @@ impl Client { self.send.is_none() } + pub fn events( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator + FusedIterator + Clone + '_ + { + self.events.iter() + } + /// Removes an [`Event`] from the event queue. /// /// If there are no remaining events, `None` is returned. /// /// Any remaining client events are deleted at the end of the /// current tick. - pub fn pop_event(&mut self) -> Option { + pub fn pop_event(&mut self) -> Option { self.events.pop_front() } - pub fn trigger_entity_event(&mut self, event: EntityEvent) { + pub fn push_entity_event(&mut self, event: EntityEvent) { self.entity_events.push(event); } - /// The current view distance of this client measured in chunks. - pub fn view_distance(&self) -> u8 { - self.settings - .as_ref() - .map_or(2, |s| s.view_distance) - .min(self.max_view_distance()) - } - - /// Gets the maximum view distance. The client will not be able to see - /// chunks and entities past this distance. + /// The current view distance of this client measured in chunks. The client + /// will not be able to see chunks and entities past this distance. /// - /// The value returned is measured in chunks. - pub fn max_view_distance(&self) -> u8 { - self.new_max_view_distance + /// The result is in `2..=32`. + pub fn view_distance(&self) -> u8 { + self.view_distance } - /// Sets the maximum view distance. The client will not be able to see - /// chunks and entities past this distance. + /// Sets the view distance. The client will not be able to see chunks and + /// entities past this distance. /// /// The new view distance is measured in chunks and is clamped to `2..=32`. - pub fn set_max_view_distance(&mut self, dist: u8) { - self.new_max_view_distance = dist.clamp(2, 32); + pub fn set_view_distance(&mut self, dist: u8) { + let dist = dist.clamp(2, 32); + + if self.view_distance != dist { + self.view_distance = dist; + self.bits.set_view_distance_modified(true); + } } /// Enables hardcore mode. This changes the design of the client's hearts. @@ -561,12 +525,12 @@ impl Client { /// To have any visible effect, this function must be called on the same /// tick the client joins the server. pub fn set_hardcore(&mut self, hardcore: bool) { - self.flags.set_hardcore(hardcore); + self.bits.set_hardcore(hardcore); } /// Gets if hardcore mode is enabled. pub fn is_hardcore(&self) -> bool { - self.flags.hardcore() + self.bits.hardcore() } /// Gets the client's current settings. @@ -630,44 +594,6 @@ impl Client { } fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) { - fn handle_movement_packet( - client: &mut Client, - _vehicle: bool, - new_position: Vec3, - new_yaw: f32, - new_pitch: f32, - new_on_ground: bool, - ) { - if client.pending_teleports == 0 { - // TODO: validate movement using swept AABB collision with the blocks. - // TODO: validate that the client is actually inside/outside the vehicle? - - // Movement packets should be coming in at a rate of STANDARD_TPS. - let new_velocity = (new_position - client.new_position).as_() * STANDARD_TPS as f32; - - let event = Event::Movement { - old_position: client.new_position, - old_velocity: client.velocity, - old_yaw: client.yaw, - old_pitch: client.pitch, - old_on_ground: client.flags.on_ground(), - new_position, - new_velocity, - new_yaw, - new_pitch, - new_on_ground, - }; - - client.new_position = new_position; - client.velocity = new_velocity; - client.yaw = new_yaw; - client.pitch = new_pitch; - client.flags.set_on_ground(new_on_ground); - - client.events.push_back(event); - } - } - match pkt { C2sPlayPacket::TeleportConfirm(p) => { if self.pending_teleports == 0 { @@ -694,14 +620,14 @@ impl Client { C2sPlayPacket::QueryBlockNbt(_) => {} C2sPlayPacket::UpdateDifficulty(_) => {} C2sPlayPacket::CommandExecution(_) => {} - C2sPlayPacket::ChatMessage(p) => self.events.push_back(Event::ChatMessage { + C2sPlayPacket::ChatMessage(p) => self.events.push_back(ClientEvent::ChatMessage { message: p.message.0, timestamp: Duration::from_millis(p.timestamp), }), C2sPlayPacket::RequestChatPreview(_) => {} C2sPlayPacket::ClientStatus(_) => {} C2sPlayPacket::ClientSettings(p) => { - let old = self.settings.replace(Settings { + self.events.push_back(ClientEvent::SettingsChanged { locale: p.locale.0, view_distance: p.view_distance.0, chat_mode: p.chat_mode, @@ -709,9 +635,7 @@ impl Client { main_hand: p.main_hand, displayed_skin_parts: p.displayed_skin_parts, allow_server_listings: p.allow_server_listings, - }); - - self.events.push_back(Event::SettingsChanged(old)); + }) } C2sPlayPacket::RequestCommandCompletion(_) => {} C2sPlayPacket::ButtonClick(_) => {} @@ -725,7 +649,7 @@ impl Client { // TODO: verify that the client has line of sight to the targeted entity and // that the distance is <=4 blocks. - self.events.push_back(Event::InteractWithEntity { + self.events.push_back(ClientEvent::InteractWithEntity { id, sneaking: p.sneaking, kind: match p.kind { @@ -741,7 +665,7 @@ impl Client { C2sPlayPacket::JigsawGenerate(_) => {} C2sPlayPacket::KeepAlive(p) => { let last_keepalive_id = self.last_keepalive_id; - if self.flags.got_keepalive() { + if self.bits.got_keepalive() { log::warn!("unexpected keepalive from player {}", self.username()); self.disconnect_no_reason(); } else if p.id != last_keepalive_id { @@ -753,39 +677,68 @@ impl Client { ); self.disconnect_no_reason(); } else { - self.flags.set_got_keepalive(true); + self.bits.set_got_keepalive(true); } } C2sPlayPacket::UpdateDifficultyLock(_) => {} C2sPlayPacket::MovePlayerPosition(p) => { - handle_movement_packet(self, false, p.position, self.yaw, self.pitch, p.on_ground) + if self.pending_teleports == 0 { + self.position = p.position; + + self.events.push_back(ClientEvent::MovePosition { + position: p.position, + on_ground: p.on_ground, + }); + } } C2sPlayPacket::MovePlayerPositionAndRotation(p) => { - handle_movement_packet(self, false, p.position, p.yaw, p.pitch, p.on_ground) + if self.pending_teleports == 0 { + self.position = p.position; + self.yaw = p.yaw; + self.pitch = p.pitch; + + self.events.push_back(ClientEvent::MovePositionAndRotation { + position: p.position, + yaw: p.yaw, + pitch: p.pitch, + on_ground: p.on_ground, + }); + } } C2sPlayPacket::MovePlayerRotation(p) => { - handle_movement_packet(self, false, self.new_position, p.yaw, p.pitch, p.on_ground) + if self.pending_teleports == 0 { + self.yaw = p.yaw; + self.pitch = p.pitch; + + self.events.push_back(ClientEvent::MoveRotation { + yaw: p.yaw, + pitch: p.pitch, + on_ground: p.on_ground, + }); + } + } + C2sPlayPacket::MovePlayerOnGround(p) => { + if self.pending_teleports == 0 { + self.events.push_back(ClientEvent::MoveOnGround { + on_ground: p.on_ground, + }); + } } - C2sPlayPacket::MovePlayerOnGround(p) => handle_movement_packet( - self, - false, - self.new_position, - self.yaw, - self.pitch, - p.on_ground, - ), C2sPlayPacket::MoveVehicle(p) => { - handle_movement_packet( - self, - true, - p.position, - p.yaw, - p.pitch, - self.flags.on_ground(), - ); + if self.pending_teleports == 0 { + self.position = p.position; + self.yaw = p.yaw; + self.pitch = p.pitch; + + self.events.push_back(ClientEvent::MoveVehicle { + position: p.position, + yaw: p.yaw, + pitch: p.pitch, + }); + } } C2sPlayPacket::BoatPaddleState(p) => { - self.events.push_back(Event::SteerBoat { + self.events.push_back(ClientEvent::SteerBoat { left_paddle_turning: p.left_paddle_turning, right_paddle_turning: p.right_paddle_turning, }); @@ -802,17 +755,17 @@ impl Client { } self.events.push_back(match p.status { - DiggingStatus::StartedDigging => Event::Digging { + DiggingStatus::StartedDigging => ClientEvent::Digging { status: event::DiggingStatus::Start, position: p.location, face: p.face, }, - DiggingStatus::CancelledDigging => Event::Digging { + DiggingStatus::CancelledDigging => ClientEvent::Digging { status: event::DiggingStatus::Cancel, position: p.location, face: p.face, }, - DiggingStatus::FinishedDigging => Event::Digging { + DiggingStatus::FinishedDigging => ClientEvent::Digging { status: event::DiggingStatus::Finish, position: p.location, face: p.face, @@ -823,56 +776,19 @@ impl Client { DiggingStatus::SwapItemInHand => return, }); } - C2sPlayPacket::PlayerCommand(e) => { - // TODO: validate: - // - Can't sprint and sneak at the same time - // - Can't leave bed while not in a bed. - // - Can't jump with a horse if not on a horse - // - Can't open horse inventory if not on a horse. - // - Can't fly with elytra if not wearing an elytra. - // - Can't jump with horse while already jumping & vice versa? - self.events.push_back(match e.action_id { - PlayerCommandId::StartSneaking => { - if self.flags.sneaking() { - return; - } - self.flags.set_sneaking(true); - Event::StartSneaking - } - PlayerCommandId::StopSneaking => { - if !self.flags.sneaking() { - return; - } - self.flags.set_sneaking(false); - Event::StopSneaking - } - PlayerCommandId::LeaveBed => Event::LeaveBed, - PlayerCommandId::StartSprinting => { - if self.flags.sprinting() { - return; - } - self.flags.set_sprinting(true); - Event::StartSprinting - } - PlayerCommandId::StopSprinting => { - if !self.flags.sprinting() { - return; - } - self.flags.set_sprinting(false); - Event::StopSprinting - } - PlayerCommandId::StartJumpWithHorse => { - self.flags.set_jumping_with_horse(true); - Event::StartJumpWithHorse { - jump_boost: e.jump_boost.0 .0 as u8, - } - } - PlayerCommandId::StopJumpWithHorse => { - self.flags.set_jumping_with_horse(false); - Event::StopJumpWithHorse - } - PlayerCommandId::OpenHorseInventory => Event::OpenHorseInventory, - PlayerCommandId::StartFlyingWithElytra => Event::StartFlyingWithElytra, + C2sPlayPacket::PlayerCommand(c) => { + self.events.push_back(match c.action_id { + PlayerCommandId::StartSneaking => ClientEvent::StartSneaking, + PlayerCommandId::StopSneaking => ClientEvent::StopSneaking, + PlayerCommandId::LeaveBed => ClientEvent::LeaveBed, + PlayerCommandId::StartSprinting => ClientEvent::StartSprinting, + PlayerCommandId::StopSprinting => ClientEvent::StopSprinting, + PlayerCommandId::StartJumpWithHorse => ClientEvent::StartJumpWithHorse { + jump_boost: c.jump_boost.0 .0 as u8, + }, + PlayerCommandId::StopJumpWithHorse => ClientEvent::StopJumpWithHorse, + PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory, + PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra, }); } C2sPlayPacket::PlayerInput(_) => {} @@ -891,7 +807,7 @@ impl Client { C2sPlayPacket::UpdateJigsaw(_) => {} C2sPlayPacket::UpdateStructureBlock(_) => {} C2sPlayPacket::UpdateSign(_) => {} - C2sPlayPacket::HandSwing(p) => self.events.push_back(Event::ArmSwing(p.hand)), + C2sPlayPacket::HandSwing(p) => self.events.push_back(ClientEvent::ArmSwing(p.hand)), C2sPlayPacket::SpectatorTeleport(_) => {} C2sPlayPacket::PlayerInteractBlock(_) => {} C2sPlayPacket::PlayerInteractItem(_) => {} @@ -926,7 +842,7 @@ impl Client { // Send the join game packet and other initial packets. We defer this until now // so that the user can set the client's location, game mode, etc. - if self.created_tick == current_tick { + if self.created_this_tick() { world .meta .player_list() @@ -941,7 +857,7 @@ impl Client { self.send_packet(GameJoin { entity_id: 0, // EntityId 0 is reserved for clients. - is_hardcore: self.flags.hardcore(), + is_hardcore: self.bits.hardcore(), gamemode: self.new_game_mode, previous_gamemode: self.old_game_mode, dimension_names, @@ -956,7 +872,7 @@ impl Client { ), hashed_seed: 0, max_players: VarInt(0), - view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), + view_distance: BoundedInt(VarInt(self.view_distance() as i32)), simulation_distance: VarInt(16), reduced_debug_info: false, enable_respawn_screen: false, @@ -969,14 +885,15 @@ impl Client { self.teleport(self.position(), self.yaw(), self.pitch()); } else { - if self.flags.spawn() { - self.flags.set_spawn(false); + if self.bits.spawn() { + self.bits.set_spawn(false); self.loaded_entities.clear(); self.loaded_chunks.clear(); // TODO: clear player list. // Client bug workaround: send the client to a dummy dimension first. + // TODO: is there actually a bug? self.send_packet(PlayerRespawn { dimension_type_name: ident!("{LIBRARY_NAMESPACE}:dimension_type_0"), dimension_name: ident!("{LIBRARY_NAMESPACE}:dummy_dimension"), @@ -1027,8 +944,8 @@ impl Client { } // Set player attributes - if self.flags.attack_speed_modified() { - self.flags.set_attack_speed_modified(false); + if self.bits.attack_speed_modified() { + self.bits.set_attack_speed_modified(false); self.send_packet(EntityAttributes { entity_id: VarInt(0), @@ -1040,8 +957,8 @@ impl Client { }); } - if self.flags.movement_speed_modified() { - self.flags.set_movement_speed_modified(false); + if self.bits.movement_speed_modified() { + self.bits.set_movement_speed_modified(false); self.send_packet(EntityAttributes { entity_id: VarInt(0), @@ -1054,8 +971,8 @@ impl Client { } // Update the players spawn position (compass position) - if self.flags.modified_spawn_position() { - self.flags.set_modified_spawn_position(false); + if self.bits.modified_spawn_position() { + self.bits.set_modified_spawn_position(false); self.send_packet(PlayerSpawnPosition { location: self.spawn_position, @@ -1063,23 +980,24 @@ impl Client { }) } - // Update view distance fog on the client if necessary. - if self.old_max_view_distance != self.new_max_view_distance { - self.old_max_view_distance = self.new_max_view_distance; - if self.created_tick != current_tick { + // Update view distance fog on the client. + if self.bits.view_distance_modified() { + self.bits.set_view_distance_modified(false); + + if !self.created_this_tick() { self.send_packet(ChunkLoadDistance { - view_distance: BoundedInt(VarInt(self.new_max_view_distance as i32)), - }) + view_distance: BoundedInt(VarInt(self.view_distance() as i32)), + }); } } // Check if it's time to send another keepalive. if current_tick % (shared.tick_rate() * 8) == 0 { - if self.flags.got_keepalive() { + if self.bits.got_keepalive() { let id = rand::random(); self.send_packet(KeepAlive { id }); self.last_keepalive_id = id; - self.flags.set_got_keepalive(false); + self.bits.set_got_keepalive(false); } else { log::warn!( "player {} timed out (no keepalive response)", @@ -1091,13 +1009,13 @@ impl Client { let view_dist = self.view_distance(); - let center = ChunkPos::at(self.new_position.x, self.new_position.z); + let center = ChunkPos::at(self.position.x, self.position.z); // Send the update view position packet if the client changes the chunk section // they're in. { let old_section = self.old_position.map(|n| (n / 16.0).floor() as i32); - let new_section = self.new_position.map(|n| (n / 16.0).floor() as i32); + let new_section = self.position.map(|n| (n / 16.0).floor() as i32); if old_section != new_section { self.send_packet(ChunkRenderDistanceCenter { @@ -1161,23 +1079,33 @@ impl Client { // // This is done after the chunks are loaded so that the "downloading terrain" // screen is closed at the appropriate time. - if self.flags.teleported_this_tick() { - self.flags.set_teleported_this_tick(false); + if self.bits.teleported_this_tick() { + self.bits.set_teleported_this_tick(false); self.send_packet(PlayerPositionLook { - position: self.new_position, + position: self.position, yaw: self.yaw, pitch: self.pitch, flags: PlayerPositionLookFlags::new(false, false, false, false, false), - teleport_id: VarInt((self.teleport_id_counter - 1) as i32), + teleport_id: VarInt(self.teleport_id_counter as i32), dismount_vehicle: false, }); + + self.pending_teleports = self.pending_teleports.wrapping_add(1); + + if self.pending_teleports == 0 { + log::warn!("too many pending teleports for {}", self.username()); + self.disconnect_no_reason(); + return; + } + + self.teleport_id_counter = self.teleport_id_counter.wrapping_add(1); } // Set velocity. Do this after teleporting since teleporting sets velocity to // zero. - if self.flags.velocity_modified() { - self.flags.set_velocity_modified(false); + if self.bits.velocity_modified() { + self.bits.set_velocity_modified(false); self.send_packet(EntityVelocityUpdate { entity_id: VarInt(0), @@ -1203,14 +1131,14 @@ impl Client { self.loaded_entities.retain(|&id| { if let Some(entity) = entities.get(id) { debug_assert!(entity.kind() != EntityKind::Marker); - if self.new_position.distance(entity.position()) <= view_dist as f64 * 16.0 { + if self.position.distance(entity.position()) <= view_dist as f64 * 16.0 { if let Some(meta) = entity.updated_tracked_data_packet(id) { send_packet(&mut self.send, meta); } let position_delta = entity.position() - entity.old_position(); let needs_teleport = position_delta.map(f64::abs).reduce_partial_max() >= 8.0; - let flags = entity.flags(); + let flags = entity.bits(); if entity.position() != entity.old_position() && !needs_teleport @@ -1345,7 +1273,8 @@ impl Client { self.entity_events.clear(); self.player_data.clear_modifications(); - self.old_position = self.new_position; + self.old_position = self.position; + self.bits.set_created_this_tick(false); } } diff --git a/src/client/event.rs b/src/client/event.rs index 6c5369f..872675f 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -13,7 +13,7 @@ pub use crate::protocol_inner::packets::s2c::play::GameMode; /// Client events can be obtained from /// [`pop_event`](crate::client::Client::pop_event). #[derive(Debug)] -pub enum Event { +pub enum ClientEvent { /// A regular message was sent to the chat. ChatMessage { /// The content of the message @@ -21,31 +21,44 @@ pub enum Event { /// The time the message was sent. timestamp: Duration, }, - /// Settings were changed. The value in this variant is the _previous_ - /// client settings. - SettingsChanged(Option), - /// The client moved. - Movement { - /// Absolute coordinates of the previous position. - old_position: Vec3, - /// Previous velocity in m/s. - old_velocity: Vec3, - /// The previous yaw (in degrees). - old_yaw: f32, - /// The previous pitch (in degrees). - old_pitch: f32, - /// If the client was previously on the ground. - old_on_ground: bool, - /// Absolute coodinates of the new position. - new_position: Vec3, - /// New velocity in m/s. - new_velocity: Vec3, - /// The new yaw (in degrees). - new_yaw: f32, - /// The new pitch (in degrees). - new_pitch: f32, - /// If the client is now on the ground. - new_on_ground: bool, + /// Settings were changed. This is always sent once after joining by the + /// vanilla client. + SettingsChanged { + /// e.g. en_US + locale: String, + /// The client side render distance, in chunks. + /// + /// The value is always in `2..=32`. + view_distance: u8, + chat_mode: ChatMode, + /// `true` if the client has chat colors enabled, `false` otherwise. + chat_colors: bool, + main_hand: MainHand, + displayed_skin_parts: DisplayedSkinParts, + allow_server_listings: bool, + }, + MovePosition { + position: Vec3, + on_ground: bool, + }, + MovePositionAndRotation { + position: Vec3, + yaw: f32, + pitch: f32, + on_ground: bool, + }, + MoveRotation { + yaw: f32, + pitch: f32, + on_ground: bool, + }, + MoveOnGround { + on_ground: bool, + }, + MoveVehicle { + position: Vec3, + yaw: f32, + pitch: f32, }, StartSneaking, StopSneaking, diff --git a/src/config.rs b/src/config.rs index c890439..670adba 100644 --- a/src/config.rs +++ b/src/config.rs @@ -90,7 +90,7 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe { /// Called once at startup to get the capacity of the buffer used to /// hold incoming packets. /// - /// A larger capcity reduces the chance of packet loss but increases + /// A larger capacity reduces the chance of packet loss but increases /// potential memory usage. /// /// # Default Implementation @@ -104,7 +104,7 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe { /// Called once at startup to get the capacity of the buffer used to /// hold outgoing packets. /// - /// A larger capcity reduces the chance of packet loss due to a full buffer + /// A larger capacity reduces the chance of packet loss due to a full buffer /// but increases potential memory usage. /// /// # Default Implementation diff --git a/src/entity.rs b/src/entity.rs index d5094ff..d43e279 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -6,7 +6,7 @@ use std::iter::FusedIterator; use std::num::NonZeroU32; use bitfield_struct::bitfield; -pub use kinds::{TrackedData, EntityKind}; +pub use data::{EntityKind, TrackedData}; use rayon::iter::ParallelIterator; use uuid::Uuid; use vek::{Aabb, Vec3}; @@ -22,7 +22,7 @@ use crate::world::WorldId; use crate::STANDARD_TPS; pub mod data; -pub mod kinds; +pub mod types; include!(concat!(env!("OUT_DIR"), "/entity_event.rs")); @@ -76,7 +76,7 @@ impl Entities { state: data, variants: TrackedData::new(kind), events: Vec::new(), - flags: EntityFlags(0), + bits: EntityBits::new(), world: WorldId::NULL, new_position: Vec3::default(), old_position: Vec3::default(), @@ -203,9 +203,9 @@ impl Entities { e.variants.clear_modifications(); e.events.clear(); - e.flags.set_yaw_or_pitch_modified(false); - e.flags.set_head_yaw_modified(false); - e.flags.set_velocity_modified(false); + e.bits.set_yaw_or_pitch_modified(false); + e.bits.set_head_yaw_modified(false); + e.bits.set_velocity_modified(false); } } } @@ -244,8 +244,8 @@ pub struct Entity { /// Custom data. pub state: C::EntityState, variants: TrackedData, - flags: EntityFlags, - events: Vec, + bits: EntityBits, + events: Vec, world: WorldId, new_position: Vec3, old_position: Vec3, @@ -257,7 +257,7 @@ pub struct Entity { } #[bitfield(u8)] -pub(crate) struct EntityFlags { +pub(crate) struct EntityBits { pub yaw_or_pitch_modified: bool, pub head_yaw_modified: bool, pub velocity_modified: bool, @@ -267,15 +267,15 @@ pub(crate) struct EntityFlags { } impl Entity { - pub(crate) fn flags(&self) -> EntityFlags { - self.flags + pub(crate) fn bits(&self) -> EntityBits { + self.bits } - pub fn view(&self) -> &TrackedData { + pub fn data(&self) -> &TrackedData { &self.variants } - pub fn view_mut(&mut self) -> &mut TrackedData { + pub fn data_mut(&mut self) -> &mut TrackedData { &mut self.variants } @@ -284,11 +284,11 @@ impl Entity { self.variants.kind() } - pub fn trigger_event(&mut self, event: Event) { + pub fn push_event(&mut self, event: EntityEvent) { self.events.push(event); } - pub(crate) fn events(&self) -> &[Event] { + pub(crate) fn events(&self) -> &[EntityEvent] { &self.events } @@ -337,7 +337,7 @@ impl Entity { pub fn set_yaw(&mut self, yaw: f32) { if self.yaw != yaw { self.yaw = yaw; - self.flags.set_yaw_or_pitch_modified(true); + self.bits.set_yaw_or_pitch_modified(true); } } @@ -350,7 +350,7 @@ impl Entity { pub fn set_pitch(&mut self, pitch: f32) { if self.pitch != pitch { self.pitch = pitch; - self.flags.set_yaw_or_pitch_modified(true); + self.bits.set_yaw_or_pitch_modified(true); } } @@ -363,7 +363,7 @@ impl Entity { pub fn set_head_yaw(&mut self, head_yaw: f32) { if self.head_yaw != head_yaw { self.head_yaw = head_yaw; - self.flags.set_head_yaw_modified(true); + self.bits.set_head_yaw_modified(true); } } @@ -378,18 +378,18 @@ impl Entity { if self.velocity != new_vel { self.velocity = new_vel; - self.flags.set_velocity_modified(true); + self.bits.set_velocity_modified(true); } } /// Gets the value of the "on ground" flag. pub fn on_ground(&self) -> bool { - self.flags.on_ground() + self.bits.on_ground() } /// Sets the value of the "on ground" flag. pub fn set_on_ground(&mut self, on_ground: bool) { - self.flags.set_on_ground(on_ground); + self.bits.set_on_ground(on_ground); } /// Gets the UUID of this entity. @@ -405,7 +405,7 @@ impl Entity { /// The hitbox of an entity is determined by its position, entity type, and /// other state specific to that type. /// - /// [interact event]: crate::client::Event::InteractWithEntity + /// [interact event]: crate::client::EntityEvent::InteractWithEntity pub fn hitbox(&self) -> Aabb { let dims = match &self.variants { TrackedData::Allay(_) => [0.6, 0.35, 0.6], diff --git a/src/entity/data.rs b/src/entity/data.rs index 4e58051..e80823c 100644 --- a/src/entity/data.rs +++ b/src/entity/data.rs @@ -1,256 +1,11 @@ -//! Primitive types used in getters and setters on entities. +//! Contains the [`TrackedData`] and the types for each variant. -use std::io::{Read, Write}; +#![allow(clippy::all, missing_docs, trivial_numeric_casts)] -use crate::protocol_inner::{Decode, Encode, VarInt}; +use crate::block::{BlockPos, BlockState}; +use crate::entity::types::*; +use crate::protocol_inner::{Encode, VarInt}; +use crate::text::Text; +use crate::uuid::Uuid; -/// Represents an optional `u32` value excluding [`u32::MAX`]. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub struct OptionalInt(u32); - -impl OptionalInt { - /// Returns `None` iff `n` is Some(u32::MAX). - pub fn new(n: impl Into>) -> Option { - match n.into() { - None => Some(Self(0)), - Some(u32::MAX) => None, - Some(n) => Some(Self(n + 1)), - } - } - - pub fn get(self) -> Option { - self.0.checked_sub(1) - } -} - -impl Encode for OptionalInt { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(self.0 as i32).encode(w) - } -} - -impl Decode for OptionalInt { - fn decode(r: &mut impl Read) -> anyhow::Result { - Ok(Self(VarInt::decode(r)?.0 as u32)) - } -} - -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -pub struct EulerAngle { - pub pitch: f32, - pub yaw: f32, - pub roll: f32, -} - -impl EulerAngle { - pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self { - Self { pitch, yaw, roll } - } -} - -impl Encode for EulerAngle { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - self.pitch.encode(w)?; - self.yaw.encode(w)?; - self.roll.encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum Facing { - Down, - Up, - North, - South, - West, - East, -} - -impl Encode for Facing { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct VillagerData { - pub kind: VillagerKind, - pub profession: VillagerProfession, - pub level: i32, -} - -impl VillagerData { - pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self { - Self { - kind, - profession, - level, - } - } -} - -impl Default for VillagerData { - fn default() -> Self { - Self { - kind: Default::default(), - profession: Default::default(), - level: 1, - } - } -} - -impl Encode for VillagerData { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(self.kind as i32).encode(w)?; - VarInt(self.profession as i32).encode(w)?; - VarInt(self.level).encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum VillagerKind { - Desert, - Jungle, - #[default] - Plains, - Savanna, - Snow, - Swamp, - Taiga, -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum VillagerProfession { - #[default] - None, - Armorer, - Butcher, - Cartographer, - Cleric, - Farmer, - Fisherman, - Fletcher, - Leatherworker, - Librarian, - Mason, - Nitwit, - Shepherd, - Toolsmith, - Weaponsmith, -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum Pose { - #[default] - Standing, - FallFlying, - Sleeping, - Swimming, - SpinAttack, - Sneaking, - LongJumping, - Dying, - Croaking, - UsingTongue, - Roaring, - Sniffing, - Emerging, - Digging, -} - -impl Encode for Pose { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} - -/// The main hand of a player. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum MainHand { - Left, - #[default] - Right, -} - -impl Encode for MainHand { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - (*self as u8).encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum BoatKind { - #[default] - Oak, - Spruce, - Birch, - Jungle, - Acacia, - DarkOak, -} - -impl Encode for BoatKind { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum CatKind { - Tabby, - #[default] - Black, - Red, - Siamese, - BritishShorthair, - Calico, - Persian, - Ragdoll, - White, - Jellie, - AllBlack, -} - -impl Encode for CatKind { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum FrogKind { - #[default] - Temperate, - Warm, - Cold, -} - -impl Encode for FrogKind { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub enum PaintingKind { - #[default] - Kebab, // TODO -} - -impl Encode for PaintingKind { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} - -// TODO -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum Particle { - EntityEffect = 21, -} - -impl Encode for Particle { - fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - VarInt(*self as i32).encode(w) - } -} +include!(concat!(env!("OUT_DIR"), "/entity.rs")); diff --git a/src/entity/kinds.rs b/src/entity/kinds.rs deleted file mode 100644 index b0dc7cd..0000000 --- a/src/entity/kinds.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Contains the [`TrackedData`] and the types for each variant. - -#![allow(clippy::all, missing_docs, trivial_numeric_casts)] - -use crate::block::{BlockPos, BlockState}; -use crate::entity::data::*; -use crate::protocol_inner::{Encode, VarInt}; -use crate::text::Text; -use crate::uuid::Uuid; - -include!(concat!(env!("OUT_DIR"), "/entity.rs")); diff --git a/src/entity/types.rs b/src/entity/types.rs new file mode 100644 index 0000000..f9ee3f1 --- /dev/null +++ b/src/entity/types.rs @@ -0,0 +1,256 @@ +//! Primitive types used in getters and setters on entities. + +use std::io::{Read, Write}; + +use crate::protocol_inner::{Decode, Encode, VarInt}; + +/// Represents an optional `u32` value excluding [`u32::MAX`]. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct OptionalInt(u32); + +impl OptionalInt { + /// Returns `None` iff `n` is Some(u32::MAX). + pub fn new(n: impl Into>) -> Option { + match n.into() { + None => Some(Self(0)), + Some(u32::MAX) => None, + Some(n) => Some(Self(n + 1)), + } + } + + pub fn get(self) -> Option { + self.0.checked_sub(1) + } +} + +impl Encode for OptionalInt { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(self.0 as i32).encode(w) + } +} + +impl Decode for OptionalInt { + fn decode(r: &mut impl Read) -> anyhow::Result { + Ok(Self(VarInt::decode(r)?.0 as u32)) + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub struct EulerAngle { + pub pitch: f32, + pub yaw: f32, + pub roll: f32, +} + +impl EulerAngle { + pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self { + Self { pitch, yaw, roll } + } +} + +impl Encode for EulerAngle { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + self.pitch.encode(w)?; + self.yaw.encode(w)?; + self.roll.encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Facing { + Down, + Up, + North, + South, + West, + East, +} + +impl Encode for Facing { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct VillagerData { + pub kind: VillagerKind, + pub profession: VillagerProfession, + pub level: i32, +} + +impl VillagerData { + pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self { + Self { + kind, + profession, + level, + } + } +} + +impl Default for VillagerData { + fn default() -> Self { + Self { + kind: Default::default(), + profession: Default::default(), + level: 1, + } + } +} + +impl Encode for VillagerData { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(self.kind as i32).encode(w)?; + VarInt(self.profession as i32).encode(w)?; + VarInt(self.level).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum VillagerKind { + Desert, + Jungle, + #[default] + Plains, + Savanna, + Snow, + Swamp, + Taiga, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum VillagerProfession { + #[default] + None, + Armorer, + Butcher, + Cartographer, + Cleric, + Farmer, + Fisherman, + Fletcher, + Leatherworker, + Librarian, + Mason, + Nitwit, + Shepherd, + Toolsmith, + Weaponsmith, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum Pose { + #[default] + Standing, + FallFlying, + Sleeping, + Swimming, + SpinAttack, + Sneaking, + LongJumping, + Dying, + Croaking, + UsingTongue, + Roaring, + Sniffing, + Emerging, + Digging, +} + +impl Encode for Pose { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +/// The main hand of a player. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum MainArm { + Left, + #[default] + Right, +} + +impl Encode for MainArm { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + (*self as u8).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum BoatKind { + #[default] + Oak, + Spruce, + Birch, + Jungle, + Acacia, + DarkOak, +} + +impl Encode for BoatKind { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum CatKind { + Tabby, + #[default] + Black, + Red, + Siamese, + BritishShorthair, + Calico, + Persian, + Ragdoll, + White, + Jellie, + AllBlack, +} + +impl Encode for CatKind { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum FrogKind { + #[default] + Temperate, + Warm, + Cold, +} + +impl Encode for FrogKind { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub enum PaintingKind { + #[default] + Kebab, // TODO +} + +impl Encode for PaintingKind { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} + +// TODO +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Particle { + EntityEffect = 21, +} + +impl Encode for Particle { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + VarInt(*self as i32).encode(w) + } +} diff --git a/src/player_list.rs b/src/player_list.rs index 2ba4b8e..8618a15 100644 --- a/src/player_list.rs +++ b/src/player_list.rs @@ -68,7 +68,7 @@ impl PlayerList { game_mode, ping, display_name: display_name.into(), - flags: EntryFlags::new().with_created_this_tick(true), + bits: EntryBits::new().with_created_this_tick(true), }); } else { e.set_game_mode(game_mode); @@ -84,7 +84,7 @@ impl PlayerList { game_mode, ping, display_name: display_name.into(), - flags: EntryFlags::new().with_created_this_tick(true), + bits: EntryBits::new().with_created_this_tick(true), }); true } @@ -211,7 +211,7 @@ impl PlayerList { let mut display_name = Vec::new(); for (&uuid, e) in self.entries.iter() { - if e.flags.created_this_tick() { + if e.bits.created_this_tick() { let mut properties = Vec::new(); if let Some(textures) = &e.textures { properties.push(Property { @@ -234,15 +234,15 @@ impl PlayerList { continue; } - if e.flags.modified_game_mode() { + if e.bits.modified_game_mode() { game_mode.push((uuid, e.game_mode)); } - if e.flags.modified_ping() { + if e.bits.modified_ping() { ping.push((uuid, VarInt(e.ping))); } - if e.flags.modified_display_name() { + if e.bits.modified_display_name() { display_name.push((uuid, e.display_name.clone())); } } @@ -276,7 +276,7 @@ impl PlayerList { pub(crate) fn update(&mut self) { for e in self.entries.values_mut() { - e.flags = EntryFlags(0); + e.bits = EntryBits::new(); } self.removed.clear(); self.modified_header_or_footer = false; @@ -290,7 +290,17 @@ pub struct PlayerListEntry { game_mode: GameMode, ping: i32, display_name: Option, - flags: EntryFlags, + bits: EntryBits, +} + +#[bitfield(u8)] +struct EntryBits { + created_this_tick: bool, + modified_game_mode: bool, + modified_ping: bool, + modified_display_name: bool, + #[bits(4)] + _pad: u8, } impl PlayerListEntry { @@ -313,7 +323,7 @@ impl PlayerListEntry { pub fn set_game_mode(&mut self, game_mode: GameMode) { if self.game_mode != game_mode { self.game_mode = game_mode; - self.flags.set_modified_game_mode(true); + self.bits.set_modified_game_mode(true); } } @@ -326,7 +336,7 @@ impl PlayerListEntry { pub fn set_ping(&mut self, ping: i32) { if self.ping != ping { self.ping = ping; - self.flags.set_modified_ping(true); + self.bits.set_modified_ping(true); } } @@ -340,17 +350,7 @@ impl PlayerListEntry { let display_name = display_name.into(); if self.display_name != display_name { self.display_name = display_name; - self.flags.set_modified_display_name(true); + self.bits.set_modified_display_name(true); } } } - -#[bitfield(u8)] -struct EntryFlags { - created_this_tick: bool, - modified_game_mode: bool, - modified_ping: bool, - modified_display_name: bool, - #[bits(4)] - _pad: u8, -} diff --git a/src/protocol_inner/packets.rs b/src/protocol_inner/packets.rs index 6828afe..b7406bc 100644 --- a/src/protocol_inner/packets.rs +++ b/src/protocol_inner/packets.rs @@ -14,8 +14,8 @@ use paste::paste; use serde::{Deserialize, Serialize}; use uuid::Uuid; use vek::Vec3; -// use {def_bitfield, def_enum, def_struct}; +// use {def_bitfield, def_enum, def_struct}; use crate::block_pos::BlockPos; use crate::ident::Ident; use crate::protocol_inner::{ @@ -121,8 +121,7 @@ macro_rules! def_struct { } impl $name { - #[allow(unused)] - const PACKET_ID: i32 = $id; + pub const PACKET_ID: i32 = $id; } )* @@ -231,8 +230,7 @@ macro_rules! def_enum { } impl $name { - #[allow(unused)] - const PACKET_ID: i32 = $id; + pub const PACKET_ID: i32 = $id; } )* } @@ -289,7 +287,7 @@ macro_rules! def_bitfield { $( #[doc = "Gets the " $bit " bit on this bitfield.\n"] $(#[$bit_attrs])* - pub fn [](self) -> bool { + pub fn $bit(self) -> bool { self.0 & <$inner_ty>::one() << <$inner_ty>::from($offset) != <$inner_ty>::zero() } @@ -313,7 +311,7 @@ macro_rules! def_bitfield { let mut s = f.debug_struct(stringify!($name)); paste! { $( - s.field(stringify!($bit), &self. []()); + s.field(stringify!($bit), &self. $bit()); )* } s.finish() diff --git a/src/server.rs b/src/server.rs index 1440a8c..adf9213 100644 --- a/src/server.rs +++ b/src/server.rs @@ -40,7 +40,9 @@ use crate::protocol_inner::packets::c2s::login::{ }; use crate::protocol_inner::packets::c2s::play::C2sPlayPacket; use crate::protocol_inner::packets::c2s::status::{QueryPing, QueryRequest}; -use crate::protocol_inner::packets::s2c::login::{EncryptionRequest, LoginCompression, LoginDisconnect, LoginSuccess}; +use crate::protocol_inner::packets::s2c::login::{ + EncryptionRequest, LoginCompression, LoginDisconnect, LoginSuccess, +}; use crate::protocol_inner::packets::s2c::play::S2cPlayPacket; use crate::protocol_inner::packets::s2c::status::{QueryPong, QueryResponse}; use crate::protocol_inner::packets::Property; @@ -459,12 +461,7 @@ fn join_player(server: &mut Server, msg: NewClientMessage) { let _ = msg.reply.send(s2c_packet_channels); - let client = Client::new( - c2s_packet_channels, - &server.shared, - msg.ncd, - C::ClientState::default(), - ); + let client = Client::new(c2s_packet_channels, msg.ncd, C::ClientState::default()); server.clients.insert(client); } @@ -713,30 +710,28 @@ async fn handle_login( c.enc.enable_compression(compression_threshold); c.dec.enable_compression(compression_threshold); - let npd = NewClientData { + let ncd = NewClientData { uuid, username, textures, remote_addr, }; - if let Err(reason) = server.0.cfg.login(server, &npd).await { + if let Err(reason) = server.0.cfg.login(server, &ncd).await { log::info!("Disconnect at login: \"{reason}\""); - c.enc - .write_packet(&LoginDisconnect { reason }) - .await?; + c.enc.write_packet(&LoginDisconnect { reason }).await?; return Ok(None); } c.enc .write_packet(&LoginSuccess { - uuid: npd.uuid, - username: npd.username.clone().into(), + uuid: ncd.uuid, + username: ncd.username.clone().into(), properties: Vec::new(), }) .await?; - Ok(Some(npd)) + Ok(Some(ncd)) } async fn handle_play(