From d7d922399aa4b69ef1aab87d00e96ea81fc5bd9b Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 24 Jun 2022 16:11:15 -0700 Subject: [PATCH] Add more client events --- src/chunk.rs | 18 +- src/client.rs | 441 ++++++++++++++++++++++++------------------- src/client/event.rs | 71 +++++++ src/entity.rs | 6 + src/lib.rs | 1 + src/packets.rs | 61 +++--- src/server.rs | 7 + src/spatial_index.rs | 2 + 8 files changed, 373 insertions(+), 234 deletions(-) create mode 100644 src/client/event.rs diff --git a/src/chunk.rs b/src/chunk.rs index 2b97799..1994e87 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -55,7 +55,7 @@ impl Chunks { pub fn get_block_state(&self, pos: impl Into) -> Option { let pos = pos.into(); - let chunk_pos = ChunkPos::new(pos.x / 16, pos.z / 16); + let chunk_pos = ChunkPos::from(pos); let chunk = self.get(chunk_pos)?; @@ -80,6 +80,10 @@ impl<'a> ChunksMut<'a> { Self(chunks) } + pub fn reborrow(&mut self) -> ChunksMut { + ChunksMut(self.0) + } + pub fn create(&mut self, pos: impl Into) -> bool { let section_count = (self.server.dimension(self.dimension).height / 16) as u32; let chunk = Chunk::new(section_count, self.server.current_tick()); @@ -110,7 +114,7 @@ impl<'a> ChunksMut<'a> { pub fn set_block_state(&mut self, pos: impl Into, block: BlockState) -> bool { let pos = pos.into(); - let chunk_pos = ChunkPos::new(pos.x / 16, pos.z / 16); + let chunk_pos = ChunkPos::from(pos); if let Some(chunk) = self.0.chunks.get_mut(&chunk_pos) { let min_y = self.0.server.dimension(self.0.dimension).min_y; @@ -269,7 +273,7 @@ impl Chunk { let chunk_section_position = (pos.x as i64) << 42 | (pos.z as i64 & 0x3fffff) << 20 - | (sect_y as i64) & 0xfffff; + | (sect_y as i64 + min_y.div_euclid(16) as i64) & 0xfffff; packet(BlockChangePacket::Multi(MultiBlockChange { chunk_section_position, @@ -401,7 +405,7 @@ impl ChunkPos { } pub fn at(x: f64, z: f64) -> Self { - Self::new((x / 16.0) as i32, (z / 16.0) as i32) + Self::new((x / 16.0).floor() as i32, (z / 16.0).floor() as i32) } } @@ -429,6 +433,12 @@ impl Into<[i32; 2]> for ChunkPos { } } +impl From for ChunkPos { + fn from(pos: BlockPos) -> Self { + Self::new(pos.x.div_euclid(16), pos.z.div_euclid(16)) + } +} + /// A 16x16x16 section of blocks, biomes, and light in a chunk. #[derive(Clone)] struct ChunkSection { diff --git a/src/client.rs b/src/client.rs index fb6b3b2..79718f6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,10 @@ +/// Contains the [`Event`] enum and related data types. +mod event; use std::collections::HashSet; use std::iter::FusedIterator; use std::ops::Deref; +pub use event::*; use flume::{Receiver, Sender, TrySendError}; use rayon::iter::ParallelIterator; use uuid::Uuid; @@ -12,16 +15,15 @@ use crate::block_pos::BlockPos; use crate::byte_angle::ByteAngle; use crate::dimension::{Dimension, DimensionEffects}; use crate::entity::{velocity_to_packet_units, EntityType}; -use crate::packets::play::c2s::C2sPlayPacket; -pub use crate::packets::play::s2c::GameMode; +use crate::packets::play::c2s::{C2sPlayPacket, DiggingStatus}; use crate::packets::play::s2c::{ - Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, - BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState, - ChangeGameStateReason, ChatTypeRegistry, DestroyEntities, DimensionType, DimensionTypeRegistry, - DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition, - EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame, - KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, RegistryCodec, - S2cPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition, + AcknowledgeBlockChanges, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, + BiomeMoodSound, BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, + ChangeGameState, ChangeGameStateReason, ChatTypeRegistry, DestroyEntities, DimensionType, + DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition, + EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame, KeepAlive, + PlayerPositionAndLook, PlayerPositionAndLookFlags, RegistryCodec, S2cPlayPacket, SpawnPosition, + UnloadChunk, UpdateViewDistance, UpdateViewPosition, }; use crate::protocol::{BoundedInt, Nbt}; use crate::server::C2sPacketChannels; @@ -156,6 +158,9 @@ pub struct Client { new_game_mode: GameMode, old_game_mode: GameMode, settings: Option, + dug_blocks: Vec, + // /// The metadata for the client's own player entity. + // player_meta: Player, } pub struct ClientMut<'a>(&'a mut Client); @@ -205,6 +210,7 @@ impl Client { new_game_mode: GameMode::Survival, old_game_mode: GameMode::Survival, settings: None, + dug_blocks: Vec::new(), } } @@ -258,7 +264,7 @@ impl Client { self.send.is_none() } - pub fn events(&self) -> &[Event] { + pub fn events(&self) -> &Vec { &self.events } @@ -288,8 +294,13 @@ impl<'a> ClientMut<'a> { ClientMut(self.0) } + pub fn events_mut(&mut self) -> &mut Vec { + &mut self.0.events + } + pub fn teleport(&mut self, pos: impl Into>, yaw: f32, pitch: f32) { self.0.new_position = pos.into(); + self.0.yaw = yaw; self.0.pitch = pitch; @@ -363,6 +374,215 @@ impl<'a> ClientMut<'a> { self.0.new_max_view_distance = dist.clamp(2, 32); } + pub(crate) fn handle_serverbound_packets(&mut self, entities: &Entities) { + self.0.events.clear(); + for _ in 0..self.recv.len() { + self.handle_serverbound_packet(entities, self.recv.try_recv().unwrap()); + } + } + + fn handle_serverbound_packet(&mut self, entities: &Entities, pkt: C2sPlayPacket) { + let client = &mut self.0; + + 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? + let event = Event::Movement { + position: client.new_position, + yaw: client.yaw, + pitch: client.pitch, + on_ground: client.on_ground, + }; + + client.new_position = new_position; + client.yaw = new_yaw; + client.pitch = new_pitch; + client.on_ground = new_on_ground; + + client.events.push(event); + } + } + + match pkt { + C2sPlayPacket::TeleportConfirm(p) => { + if client.pending_teleports == 0 { + self.disconnect("Unexpected teleport confirmation"); + return; + } + + let got = p.teleport_id.0 as u32; + let expected = client + .teleport_id_counter + .wrapping_sub(client.pending_teleports); + + if got == expected { + client.pending_teleports -= 1; + } else { + self.disconnect(format!( + "Unexpected teleport ID (expected {expected}, got {got})" + )); + } + } + C2sPlayPacket::QueryBlockNbt(_) => {} + C2sPlayPacket::SetDifficulty(_) => {} + C2sPlayPacket::ChatCommand(_) => {} + C2sPlayPacket::ChatMessage(_) => {} + C2sPlayPacket::ChatPreview(_) => {} + C2sPlayPacket::ClientStatus(_) => {} + C2sPlayPacket::ClientSettings(p) => { + let old = client.settings.replace(Settings { + locale: p.locale.0, + view_distance: p.view_distance.0, + chat_mode: p.chat_mode, + chat_colors: p.chat_colors, + main_hand: p.main_hand, + displayed_skin_parts: p.displayed_skin_parts, + allow_server_listings: p.allow_server_listings, + }); + + client.events.push(Event::SettingsChanged(old)); + } + C2sPlayPacket::TabComplete(_) => {} + C2sPlayPacket::ClickWindowButton(_) => {} + C2sPlayPacket::CloseWindow(_) => {} + C2sPlayPacket::PluginMessage(_) => {} + C2sPlayPacket::EditBook(_) => {} + C2sPlayPacket::QueryEntityNbt(_) => {} + C2sPlayPacket::InteractEntity(p) => { + if let Some(id) = entities.get_with_network_id(p.entity_id.0) { + // TODO: verify that the client has line of sight to the targeted entity and + // that the distance is <=4 blocks. + + use crate::packets::play::c2s::InteractType; + client.events.push(Event::InteractWithEntity { + id, + sneaking: p.sneaking, + typ: match p.typ { + InteractType::Interact(hand) => InteractWithEntity::Interact(hand), + InteractType::Attack => InteractWithEntity::Attack, + InteractType::InteractAt((target, hand)) => { + InteractWithEntity::InteractAt { target, hand } + } + }, + }); + } + } + C2sPlayPacket::GenerateStructure(_) => {} + C2sPlayPacket::KeepAlive(p) => { + let last_keepalive_id = client.last_keepalive_id; + if client.got_keepalive { + self.disconnect("Unexpected keepalive"); + } else if p.id != last_keepalive_id { + self.disconnect(format!( + "Keepalive ids don't match (expected {}, got {})", + last_keepalive_id, p.id + )); + } else { + client.got_keepalive = true; + } + } + C2sPlayPacket::LockDifficulty(_) => {} + C2sPlayPacket::PlayerPosition(p) => handle_movement_packet( + client, + false, + p.position, + client.yaw, + client.pitch, + p.on_ground, + ), + C2sPlayPacket::PlayerPositionAndRotation(p) => { + handle_movement_packet(client, false, p.position, p.yaw, p.pitch, p.on_ground) + } + C2sPlayPacket::PlayerRotation(p) => handle_movement_packet( + client, + false, + client.new_position, + p.yaw, + p.pitch, + p.on_ground, + ), + C2sPlayPacket::PlayerMovement(p) => handle_movement_packet( + client, + false, + client.new_position, + client.yaw, + client.pitch, + p.on_ground, + ), + C2sPlayPacket::VehicleMove(p) => { + handle_movement_packet(client, true, p.position, p.yaw, p.pitch, client.on_ground); + } + C2sPlayPacket::SteerBoat(p) => { + client.events.push(Event::SteerBoat { + left_paddle_turning: p.left_paddle_turning, + right_paddle_turning: p.right_paddle_turning, + }); + } + C2sPlayPacket::PickItem(_) => {} + C2sPlayPacket::CraftRecipeRequest(_) => {} + C2sPlayPacket::PlayerAbilities(_) => {} + C2sPlayPacket::PlayerDigging(p) => { + // TODO: verify dug block is within the correct distance from the client. + // TODO: verify that the broken block is allowed to be broken? + + if p.sequence.0 != 0 { + client.dug_blocks.push(p.sequence.0); + } + + client.events.push(match p.status { + DiggingStatus::StartedDigging => Event::Digging(Digging { + status: event::DiggingStatus::Start, + position: p.location, + face: p.face, + }), + DiggingStatus::CancelledDigging => Event::Digging(Digging { + status: event::DiggingStatus::Cancel, + position: p.location, + face: p.face, + }), + DiggingStatus::FinishedDigging => Event::Digging(Digging { + status: event::DiggingStatus::Finish, + position: p.location, + face: p.face, + }), + DiggingStatus::DropItemStack => return, + DiggingStatus::DropItem => return, + DiggingStatus::ShootArrowOrFinishEating => return, + DiggingStatus::SwapItemInHand => return, + }); + } + C2sPlayPacket::EntityAction(_) => {} + C2sPlayPacket::SteerVehicle(_) => {} + C2sPlayPacket::Pong(_) => {} + C2sPlayPacket::SetRecipeBookState(_) => {} + C2sPlayPacket::SetDisplayedRecipe(_) => {} + C2sPlayPacket::NameItem(_) => {} + C2sPlayPacket::ResourcePackStatus(_) => {} + C2sPlayPacket::AdvancementTab(_) => {} + C2sPlayPacket::SelectTrade(_) => {} + C2sPlayPacket::SetBeaconEffect(_) => {} + C2sPlayPacket::HeldItemChange(_) => {} + C2sPlayPacket::UpdateCommandBlock(_) => {} + C2sPlayPacket::UpdateCommandBlockMinecart(_) => {} + C2sPlayPacket::CreativeInventoryAction(_) => {} + C2sPlayPacket::UpdateJigsawBlock(_) => {} + C2sPlayPacket::UpdateStructureBlock(_) => {} + C2sPlayPacket::UpdateSign(_) => {} + C2sPlayPacket::PlayerArmSwing(_) => {} + C2sPlayPacket::Spectate(_) => {} + C2sPlayPacket::PlayerBlockPlacement(_) => {} + C2sPlayPacket::UseItem(_) => {} + } + } + pub(crate) fn update( &mut self, server: &Server, @@ -371,18 +591,7 @@ impl<'a> ClientMut<'a> { chunks: &Chunks, meta: &WorldMeta, ) { - self.0.events.clear(); - - if self.is_disconnected() { - return; - } - - for _ in 0..self.recv.len() { - self.handle_serverbound_packet(self.recv.try_recv().unwrap()); - } - // Mark the client as disconnected when appropriate. - // We do this check after handling serverbound packets so that none are lost. if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) { self.0.send = None; return; @@ -454,7 +663,7 @@ impl<'a> ClientMut<'a> { if current_tick % (server.tick_rate() * 8) == 0 { if self.0.got_keepalive { let id = rand::random(); - self.send_packet(KeepAliveClientbound { id }); + self.send_packet(KeepAlive { id }); self.0.last_keepalive_id = id; self.0.got_keepalive = false; } else { @@ -464,16 +673,13 @@ impl<'a> ClientMut<'a> { let view_dist = self.view_distance(); - let center = ChunkPos::new( - (self.new_position.x / 16.0) as i32, - (self.new_position.z / 16.0) as i32, - ); + let center = ChunkPos::at(self.new_position.x, self.new_position.z); // Send the update view position packet if the client changes the chunk section // they're in. { - let old_section = self.0.old_position.map(|n| (n / 16.0) as i32); - let new_section = self.0.new_position.map(|n| (n / 16.0) as i32); + let old_section = self.0.old_position.map(|n| (n / 16.0).floor() as i32); + let new_section = self.0.new_position.map(|n| (n / 16.0).floor() as i32); if old_section != new_section { self.send_packet(UpdateViewPosition { @@ -523,6 +729,16 @@ impl<'a> ClientMut<'a> { } } + // Acknowledge broken blocks. + for seq in self.0.dug_blocks.drain(..) { + send_packet( + &mut self.0.send, + AcknowledgeBlockChanges { + sequence: VarInt(seq), + }, + ) + } + // This is done after the chunks are loaded so that the "downloading terrain" // screen is closed at the appropriate time. if self.0.teleported_this_tick { @@ -667,141 +883,6 @@ impl<'a> ClientMut<'a> { self.0.old_position = self.0.new_position; } - - fn handle_serverbound_packet(&mut self, pkt: C2sPlayPacket) { - let client = &mut self.0; - - fn handle_movement_packet( - client: &mut Client, - new_position: Vec3, - new_yaw: f32, - new_pitch: f32, - new_on_ground: bool, - ) { - if client.pending_teleports == 0 { - let event = Event::Movement { - position: client.new_position, - yaw: client.yaw, - pitch: client.pitch, - on_ground: client.on_ground, - }; - - client.new_position = new_position; - client.yaw = new_yaw; - client.pitch = new_pitch; - client.on_ground = new_on_ground; - - client.events.push(event); - } - } - - match pkt { - C2sPlayPacket::TeleportConfirm(p) => { - if client.pending_teleports == 0 { - self.disconnect("Unexpected teleport confirmation"); - return; - } - - let got = p.teleport_id.0 as u32; - let expected = client - .teleport_id_counter - .wrapping_sub(client.pending_teleports); - - if got == expected { - client.pending_teleports -= 1; - } else { - self.disconnect(format!( - "Unexpected teleport ID (expected {expected}, got {got})" - )); - } - } - C2sPlayPacket::QueryBlockNbt(_) => {} - C2sPlayPacket::SetDifficulty(_) => {} - C2sPlayPacket::ChatCommand(_) => {} - C2sPlayPacket::ChatMessageServerbound(_) => {} - C2sPlayPacket::ChatPreview(_) => {} - C2sPlayPacket::ClientStatus(_) => {} - C2sPlayPacket::ClientSettings(p) => { - let old = client.settings.replace(Settings { - locale: p.locale.0, - view_distance: p.view_distance.0, - chat_mode: p.chat_mode, - chat_colors: p.chat_colors, - main_hand: p.main_hand, - displayed_skin_parts: p.displayed_skin_parts, - allow_server_listings: p.allow_server_listings, - }); - - client.events.push(Event::SettingsChanged(old)); - } - C2sPlayPacket::TabCompleteServerbound(_) => {} - C2sPlayPacket::ClickWindowButton(_) => {} - C2sPlayPacket::CloseWindow(_) => {} - C2sPlayPacket::PluginMessageServerbound(_) => {} - C2sPlayPacket::EditBook(_) => {} - C2sPlayPacket::QueryEntityNbt(_) => {} - C2sPlayPacket::InteractEntity(_) => {} - C2sPlayPacket::GenerateStructure(_) => {} - C2sPlayPacket::KeepAliveServerbound(p) => { - let last_keepalive_id = client.last_keepalive_id; - if client.got_keepalive { - self.disconnect("Unexpected keepalive"); - } else if p.id != last_keepalive_id { - self.disconnect(format!( - "Keepalive ids don't match (expected {}, got {})", - last_keepalive_id, p.id - )); - } else { - client.got_keepalive = true; - } - } - C2sPlayPacket::LockDifficulty(_) => {} - C2sPlayPacket::PlayerPosition(p) => { - handle_movement_packet(client, p.position, client.yaw, client.pitch, p.on_ground) - } - C2sPlayPacket::PlayerPositionAndRotation(p) => { - handle_movement_packet(client, p.position, p.yaw, p.pitch, p.on_ground) - } - C2sPlayPacket::PlayerRotation(p) => { - handle_movement_packet(client, client.new_position, p.yaw, p.pitch, p.on_ground) - } - - C2sPlayPacket::PlayerMovement(p) => handle_movement_packet( - client, - client.new_position, - client.yaw, - client.pitch, - p.on_ground, - ), - C2sPlayPacket::VehicleMoveServerbound(_) => {} - C2sPlayPacket::SteerBoat(_) => {} - C2sPlayPacket::PickItem(_) => {} - C2sPlayPacket::CraftRecipeRequest(_) => {} - C2sPlayPacket::PlayerAbilitiesServerbound(_) => {} - C2sPlayPacket::PlayerDigging(_) => {} - C2sPlayPacket::EntityAction(_) => {} - C2sPlayPacket::SteerVehicle(_) => {} - C2sPlayPacket::Pong(_) => {} - C2sPlayPacket::SetRecipeBookState(_) => {} - C2sPlayPacket::SetDisplayedRecipe(_) => {} - C2sPlayPacket::NameItem(_) => {} - C2sPlayPacket::ResourcePackStatus(_) => {} - C2sPlayPacket::AdvancementTab(_) => {} - C2sPlayPacket::SelectTrade(_) => {} - C2sPlayPacket::SetBeaconEffect(_) => {} - C2sPlayPacket::HeldItemChangeServerbound(_) => {} - C2sPlayPacket::UpdateCommandBlock(_) => {} - C2sPlayPacket::UpdateCommandBlockMinecart(_) => {} - C2sPlayPacket::CreativeInventoryAction(_) => {} - C2sPlayPacket::UpdateJigsawBlock(_) => {} - C2sPlayPacket::UpdateStructureBlock(_) => {} - C2sPlayPacket::UpdateSign(_) => {} - C2sPlayPacket::PlayerArmSwing(_) => {} - C2sPlayPacket::Spectate(_) => {} - C2sPlayPacket::PlayerBlockPlacement(_) => {} - C2sPlayPacket::UseItem(_) => {} - } - } } impl Drop for Client { @@ -810,40 +891,6 @@ impl Drop for Client { } } -#[derive(Debug)] -pub enum Event { - /// Settings were changed. The value in this variant is the previous client - /// settings. - SettingsChanged(Option), - - /// The client has moved. The values in this variant are the previous - /// position and look. - Movement { - position: Vec3, - yaw: f32, - pitch: f32, - on_ground: bool, - }, -} - -#[derive(Clone, PartialEq, Debug)] -pub struct Settings { - /// e.g. en_US - pub locale: String, - /// The client side render distance, in chunks. - /// - /// The value is always in `2..=32`. - pub view_distance: u8, - pub chat_mode: ChatMode, - /// `true` if the client has chat colors enabled, `false` otherwise. - pub chat_colors: bool, - pub main_hand: MainHand, - pub displayed_skin_parts: DisplayedSkinParts, - pub allow_server_listings: bool, -} - -pub use crate::packets::play::c2s::{ChatMode, DisplayedSkinParts, MainHand}; - fn send_packet(send_opt: &mut Option>, pkt: impl Into) { if let Some(send) = send_opt { match send.try_send(pkt.into()) { diff --git a/src/client/event.rs b/src/client/event.rs new file mode 100644 index 0000000..c7cb2f7 --- /dev/null +++ b/src/client/event.rs @@ -0,0 +1,71 @@ +use vek::Vec3; + +use crate::packets::play::c2s::BlockFace; +pub use crate::packets::play::c2s::{ChatMode, DisplayedSkinParts, Hand, MainHand}; +pub use crate::packets::play::s2c::GameMode; +use crate::{BlockPos, EntityId}; + +#[derive(Debug)] +pub enum Event { + /// Settings were changed. The value in this variant is the previous client + /// settings. + SettingsChanged(Option), + /// The client has moved. The values in this + /// variant are the _previous_ position and look. + Movement { + position: Vec3, + yaw: f32, + pitch: f32, + on_ground: bool, + }, + InteractWithEntity { + /// The ID of the entity being interacted with. + id: EntityId, + /// If the client was sneaking during the interaction. + sneaking: bool, + /// The type of interaction that occurred. + typ: InteractWithEntity, + }, + SteerBoat { + left_paddle_turning: bool, + right_paddle_turning: bool, + }, + Digging(Digging), +} + +#[derive(Clone, PartialEq, Debug)] +pub struct Settings { + /// e.g. en_US + pub locale: String, + /// The client side render distance, in chunks. + /// + /// The value is always in `2..=32`. + pub view_distance: u8, + pub chat_mode: ChatMode, + /// `true` if the client has chat colors enabled, `false` otherwise. + pub chat_colors: bool, + pub main_hand: MainHand, + pub displayed_skin_parts: DisplayedSkinParts, + pub allow_server_listings: bool, +} + +#[derive(Clone, PartialEq, Debug)] +pub enum InteractWithEntity { + Interact(Hand), + InteractAt { target: Vec3, hand: Hand }, + Attack, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Digging { + pub status: DiggingStatus, + pub position: BlockPos, + pub face: BlockFace, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum DiggingStatus { + Start, + Cancel, + Finish, +} diff --git a/src/entity.rs b/src/entity.rs index 8974449..de349c3 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -64,6 +64,12 @@ impl Entities { self.sm.get(entity.0) } + pub(crate) fn get_with_network_id(&self, network_id: i32) -> Option { + let version = NonZeroU32::new(network_id as u32)?; + let index = *self.network_id_to_entity.get(&version)?; + Some(EntityId(Key::new(index, version))) + } + pub fn iter(&self) -> impl FusedIterator + Clone + '_ { self.sm.iter().map(|(k, v)| (EntityId(k), v)) } diff --git a/src/lib.rs b/src/lib.rs index d8c7f51..d8328cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ pub mod world; pub use async_trait::async_trait; pub use biome::{Biome, BiomeId}; +pub use block::BlockState; pub use block_pos::BlockPos; pub use chunk::{Chunk, ChunkPos, Chunks, ChunksMut}; pub use client::{Client, ClientMut, Clients, ClientsMut}; diff --git a/src/packets.rs b/src/packets.rs index f89e84f..f626ec6 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -472,9 +472,9 @@ pub mod login { /// Packets and types used during the play state. pub mod play { pub mod s2c { - use crate::packets::login::{s2c::Property, c2s::SignatureData}; - use super::super::*; + use crate::packets::login::c2s::SignatureData; + use crate::packets::login::s2c::Property; def_struct! { SpawnEntity 0x00 { @@ -536,7 +536,7 @@ pub mod play { } def_struct! { - AcknoledgeBlockChanges 0x05 { + AcknowledgeBlockChanges 0x05 { sequence: VarInt, } } @@ -749,7 +749,7 @@ pub mod play { } def_struct! { - KeepAliveClientbound 0x1e { + KeepAlive 0x1e { id: i64, } } @@ -995,7 +995,7 @@ pub mod play { } def_struct! { - ChatMessageClientbound 0x30 { + ChatMessage 0x30 { message: Text, typ: ChatMessageType, sender: Uuid, @@ -1081,7 +1081,7 @@ pub mod play { } def_struct! { - HeldItemChangeClientbound 0x47 { + HeldItemChange 0x47 { slot: BoundedInt, } } @@ -1203,7 +1203,7 @@ pub mod play { SpawnExperienceOrb, SpawnPlayer, EntityAnimation, - AcknoledgeBlockChanges, + AcknowledgeBlockChanges, BlockBreakAnimation, BlockEntityData, BlockAction, @@ -1213,7 +1213,7 @@ pub mod play { EntityStatus, UnloadChunk, ChangeGameState, - KeepAliveClientbound, + KeepAlive, ChunkDataAndUpdateLight, JoinGame, EntityPosition, @@ -1223,7 +1223,7 @@ pub mod play { DestroyEntities, EntityHeadLook, MultiBlockChange, - HeldItemChangeClientbound, + HeldItemChange, UpdateViewPosition, UpdateViewDistance, SpawnPosition, @@ -1268,9 +1268,9 @@ pub mod play { } def_struct! { - ChatMessageServerbound 0x04 { + ChatMessage 0x04 { message: BoundedString<0, 256> - // TODO: + // TODO: } } @@ -1337,7 +1337,7 @@ pub mod play { } def_struct! { - TabCompleteServerbound 0x08 { + TabComplete 0x08 { transaction_id: VarInt, /// Text behind the cursor without the '/'. text: BoundedString<0, 32500> @@ -1358,7 +1358,7 @@ pub mod play { } def_struct! { - PluginMessageServerbound 0x0c { + PluginMessage 0x0c { channel: Ident, data: RawBytes, } @@ -1391,18 +1391,12 @@ pub mod play { InteractType: VarInt { Interact: Hand = 0, Attack = 1, - InteractAt: InteractAtData = 2 - } - } - - def_struct! { - InteractAtData { - target: Vec3, - hand: Hand, + InteractAt: (Vec3, Hand) = 2 } } def_enum! { + #[derive(Copy, PartialEq, Eq)] Hand: VarInt { Main = 0, Off = 1, @@ -1418,7 +1412,7 @@ pub mod play { } def_struct! { - KeepAliveServerbound 0x11 { + KeepAlive 0x11 { id: i64, } } @@ -1465,7 +1459,7 @@ pub mod play { } def_struct! { - VehicleMoveServerbound 0x17 { + VehicleMove 0x17 { /// Absolute position position: Vec3, /// Degrees @@ -1497,7 +1491,7 @@ pub mod play { } def_enum! { - PlayerAbilitiesServerbound 0x1b: i8 { + PlayerAbilities 0x1b: i8 { NotFlying = 0, Flying = 0b10, } @@ -1525,6 +1519,7 @@ pub mod play { } def_enum! { + #[derive(Copy, PartialEq, Eq)] BlockFace: i8 { /// -Y Bottom = 0, @@ -1644,7 +1639,7 @@ pub mod play { } def_struct! { - HeldItemChangeServerbound 0x27 { + HeldItemChange 0x27 { slot: BoundedInt, } } @@ -1784,7 +1779,7 @@ pub mod play { hand: Hand, location: BlockPos, face: BlockFace, - cursor_pos: Vec3, + cursor_pos: Vec3, head_inside_block: bool, sequence: VarInt, } @@ -1852,29 +1847,29 @@ pub mod play { QueryBlockNbt, SetDifficulty, ChatCommand, - ChatMessageServerbound, + ChatMessage, ChatPreview, ClientStatus, ClientSettings, - TabCompleteServerbound, + TabComplete, ClickWindowButton, CloseWindow, - PluginMessageServerbound, + PluginMessage, EditBook, QueryEntityNbt, InteractEntity, GenerateStructure, - KeepAliveServerbound, + KeepAlive, LockDifficulty, PlayerPosition, PlayerPositionAndRotation, PlayerRotation, PlayerMovement, - VehicleMoveServerbound, + VehicleMove, SteerBoat, PickItem, CraftRecipeRequest, - PlayerAbilitiesServerbound, + PlayerAbilities, PlayerDigging, EntityAction, SteerVehicle, @@ -1886,7 +1881,7 @@ pub mod play { AdvancementTab, SelectTrade, SetBeaconEffect, - HeldItemChangeServerbound, + HeldItemChange, UpdateCommandBlock, UpdateCommandBlockMinecart, CreativeInventoryAction, diff --git a/src/server.rs b/src/server.rs index 876868e..30b8d40 100644 --- a/src/server.rs +++ b/src/server.rs @@ -360,6 +360,13 @@ fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult { join_player(&server, worlds.reborrow(), msg); } + // Get serverbound packets first so they are not dealt with a tick late. + worlds.par_iter_mut().for_each(|(_, mut world)| { + world.clients.par_iter_mut().for_each(|(_, mut client)| { + client.handle_serverbound_packets(&world.entities); + }); + }); + server.config().update(&server, worlds.reborrow()); worlds.par_iter_mut().for_each(|(_, mut world)| { diff --git a/src/spatial_index.rs b/src/spatial_index.rs index d2cc568..af9414c 100644 --- a/src/spatial_index.rs +++ b/src/spatial_index.rs @@ -47,6 +47,8 @@ impl SpatialIndex { }) } + // TODO: accept predicate here. Might want to skip invisible entities, for + // instance. pub fn raycast(&self, origin: Vec3, direction: Vec3) -> Option { debug_assert!( direction.is_normalized(),