Add more client events

This commit is contained in:
Ryan 2022-06-24 16:11:15 -07:00
parent a58258e8d3
commit d7d922399a
8 changed files with 373 additions and 234 deletions

View file

@ -55,7 +55,7 @@ impl Chunks {
pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> { pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> {
let pos = pos.into(); 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)?; let chunk = self.get(chunk_pos)?;
@ -80,6 +80,10 @@ impl<'a> ChunksMut<'a> {
Self(chunks) Self(chunks)
} }
pub fn reborrow(&mut self) -> ChunksMut {
ChunksMut(self.0)
}
pub fn create(&mut self, pos: impl Into<ChunkPos>) -> bool { pub fn create(&mut self, pos: impl Into<ChunkPos>) -> bool {
let section_count = (self.server.dimension(self.dimension).height / 16) as u32; let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
let chunk = Chunk::new(section_count, self.server.current_tick()); 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<BlockPos>, block: BlockState) -> bool { pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
let pos = pos.into(); 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) { if let Some(chunk) = self.0.chunks.get_mut(&chunk_pos) {
let min_y = self.0.server.dimension(self.0.dimension).min_y; 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 let chunk_section_position = (pos.x as i64) << 42
| (pos.z as i64 & 0x3fffff) << 20 | (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 { packet(BlockChangePacket::Multi(MultiBlockChange {
chunk_section_position, chunk_section_position,
@ -401,7 +405,7 @@ impl ChunkPos {
} }
pub fn at(x: f64, z: f64) -> Self { 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<BlockPos> 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. /// A 16x16x16 section of blocks, biomes, and light in a chunk.
#[derive(Clone)] #[derive(Clone)]
struct ChunkSection { struct ChunkSection {

View file

@ -1,7 +1,10 @@
/// Contains the [`Event`] enum and related data types.
mod event;
use std::collections::HashSet; use std::collections::HashSet;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::ops::Deref; use std::ops::Deref;
pub use event::*;
use flume::{Receiver, Sender, TrySendError}; use flume::{Receiver, Sender, TrySendError};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use uuid::Uuid; use uuid::Uuid;
@ -12,16 +15,15 @@ use crate::block_pos::BlockPos;
use crate::byte_angle::ByteAngle; use crate::byte_angle::ByteAngle;
use crate::dimension::{Dimension, DimensionEffects}; use crate::dimension::{Dimension, DimensionEffects};
use crate::entity::{velocity_to_packet_units, EntityType}; use crate::entity::{velocity_to_packet_units, EntityType};
use crate::packets::play::c2s::C2sPlayPacket; use crate::packets::play::c2s::{C2sPlayPacket, DiggingStatus};
pub use crate::packets::play::s2c::GameMode;
use crate::packets::play::s2c::{ use crate::packets::play::s2c::{
Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, AcknowledgeBlockChanges, Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects,
BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState, BiomeMoodSound, BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry,
ChangeGameStateReason, ChatTypeRegistry, DestroyEntities, DimensionType, DimensionTypeRegistry, ChangeGameState, ChangeGameStateReason, ChatTypeRegistry, DestroyEntities, DimensionType,
DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition, DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition,
EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame, EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame, KeepAlive,
KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, RegistryCodec, PlayerPositionAndLook, PlayerPositionAndLookFlags, RegistryCodec, S2cPlayPacket, SpawnPosition,
S2cPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition,
}; };
use crate::protocol::{BoundedInt, Nbt}; use crate::protocol::{BoundedInt, Nbt};
use crate::server::C2sPacketChannels; use crate::server::C2sPacketChannels;
@ -156,6 +158,9 @@ pub struct Client {
new_game_mode: GameMode, new_game_mode: GameMode,
old_game_mode: GameMode, old_game_mode: GameMode,
settings: Option<Settings>, settings: Option<Settings>,
dug_blocks: Vec<i32>,
// /// The metadata for the client's own player entity.
// player_meta: Player,
} }
pub struct ClientMut<'a>(&'a mut Client); pub struct ClientMut<'a>(&'a mut Client);
@ -205,6 +210,7 @@ impl Client {
new_game_mode: GameMode::Survival, new_game_mode: GameMode::Survival,
old_game_mode: GameMode::Survival, old_game_mode: GameMode::Survival,
settings: None, settings: None,
dug_blocks: Vec::new(),
} }
} }
@ -258,7 +264,7 @@ impl Client {
self.send.is_none() self.send.is_none()
} }
pub fn events(&self) -> &[Event] { pub fn events(&self) -> &Vec<Event> {
&self.events &self.events
} }
@ -288,8 +294,13 @@ impl<'a> ClientMut<'a> {
ClientMut(self.0) ClientMut(self.0)
} }
pub fn events_mut(&mut self) -> &mut Vec<Event> {
&mut self.0.events
}
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) { pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
self.0.new_position = pos.into(); self.0.new_position = pos.into();
self.0.yaw = yaw; self.0.yaw = yaw;
self.0.pitch = pitch; self.0.pitch = pitch;
@ -363,6 +374,215 @@ impl<'a> ClientMut<'a> {
self.0.new_max_view_distance = dist.clamp(2, 32); 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<f64>,
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( pub(crate) fn update(
&mut self, &mut self,
server: &Server, server: &Server,
@ -371,18 +591,7 @@ impl<'a> ClientMut<'a> {
chunks: &Chunks, chunks: &Chunks,
meta: &WorldMeta, 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. // 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()) { if self.recv.is_disconnected() || self.send.as_ref().map_or(true, |s| s.is_disconnected()) {
self.0.send = None; self.0.send = None;
return; return;
@ -454,7 +663,7 @@ impl<'a> ClientMut<'a> {
if current_tick % (server.tick_rate() * 8) == 0 { if current_tick % (server.tick_rate() * 8) == 0 {
if self.0.got_keepalive { if self.0.got_keepalive {
let id = rand::random(); let id = rand::random();
self.send_packet(KeepAliveClientbound { id }); self.send_packet(KeepAlive { id });
self.0.last_keepalive_id = id; self.0.last_keepalive_id = id;
self.0.got_keepalive = false; self.0.got_keepalive = false;
} else { } else {
@ -464,16 +673,13 @@ impl<'a> ClientMut<'a> {
let view_dist = self.view_distance(); let view_dist = self.view_distance();
let center = ChunkPos::new( let center = ChunkPos::at(self.new_position.x, self.new_position.z);
(self.new_position.x / 16.0) as i32,
(self.new_position.z / 16.0) as i32,
);
// Send the update view position packet if the client changes the chunk section // Send the update view position packet if the client changes the chunk section
// they're in. // they're in.
{ {
let old_section = self.0.old_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) as i32); let new_section = self.0.new_position.map(|n| (n / 16.0).floor() as i32);
if old_section != new_section { if old_section != new_section {
self.send_packet(UpdateViewPosition { 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" // This is done after the chunks are loaded so that the "downloading terrain"
// screen is closed at the appropriate time. // screen is closed at the appropriate time.
if self.0.teleported_this_tick { if self.0.teleported_this_tick {
@ -667,141 +883,6 @@ impl<'a> ClientMut<'a> {
self.0.old_position = self.0.new_position; 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<f64>,
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 { 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<Settings>),
/// The client has moved. The values in this variant are the previous
/// position and look.
Movement {
position: Vec3<f64>,
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<Sender<S2cPlayPacket>>, pkt: impl Into<S2cPlayPacket>) { fn send_packet(send_opt: &mut Option<Sender<S2cPlayPacket>>, pkt: impl Into<S2cPlayPacket>) {
if let Some(send) = send_opt { if let Some(send) = send_opt {
match send.try_send(pkt.into()) { match send.try_send(pkt.into()) {

71
src/client/event.rs Normal file
View file

@ -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<Settings>),
/// The client has moved. The values in this
/// variant are the _previous_ position and look.
Movement {
position: Vec3<f64>,
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<f32>, 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,
}

View file

@ -64,6 +64,12 @@ impl Entities {
self.sm.get(entity.0) self.sm.get(entity.0)
} }
pub(crate) fn get_with_network_id(&self, network_id: i32) -> Option<EntityId> {
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<Item = (EntityId, &Entity)> + Clone + '_ { pub fn iter(&self) -> impl FusedIterator<Item = (EntityId, &Entity)> + Clone + '_ {
self.sm.iter().map(|(k, v)| (EntityId(k), v)) self.sm.iter().map(|(k, v)| (EntityId(k), v))
} }

View file

@ -32,6 +32,7 @@ pub mod world;
pub use async_trait::async_trait; pub use async_trait::async_trait;
pub use biome::{Biome, BiomeId}; pub use biome::{Biome, BiomeId};
pub use block::BlockState;
pub use block_pos::BlockPos; pub use block_pos::BlockPos;
pub use chunk::{Chunk, ChunkPos, Chunks, ChunksMut}; pub use chunk::{Chunk, ChunkPos, Chunks, ChunksMut};
pub use client::{Client, ClientMut, Clients, ClientsMut}; pub use client::{Client, ClientMut, Clients, ClientsMut};

View file

@ -472,9 +472,9 @@ pub mod login {
/// Packets and types used during the play state. /// Packets and types used during the play state.
pub mod play { pub mod play {
pub mod s2c { pub mod s2c {
use crate::packets::login::{s2c::Property, c2s::SignatureData};
use super::super::*; use super::super::*;
use crate::packets::login::c2s::SignatureData;
use crate::packets::login::s2c::Property;
def_struct! { def_struct! {
SpawnEntity 0x00 { SpawnEntity 0x00 {
@ -536,7 +536,7 @@ pub mod play {
} }
def_struct! { def_struct! {
AcknoledgeBlockChanges 0x05 { AcknowledgeBlockChanges 0x05 {
sequence: VarInt, sequence: VarInt,
} }
} }
@ -749,7 +749,7 @@ pub mod play {
} }
def_struct! { def_struct! {
KeepAliveClientbound 0x1e { KeepAlive 0x1e {
id: i64, id: i64,
} }
} }
@ -995,7 +995,7 @@ pub mod play {
} }
def_struct! { def_struct! {
ChatMessageClientbound 0x30 { ChatMessage 0x30 {
message: Text, message: Text,
typ: ChatMessageType, typ: ChatMessageType,
sender: Uuid, sender: Uuid,
@ -1081,7 +1081,7 @@ pub mod play {
} }
def_struct! { def_struct! {
HeldItemChangeClientbound 0x47 { HeldItemChange 0x47 {
slot: BoundedInt<u8, 0, 9>, slot: BoundedInt<u8, 0, 9>,
} }
} }
@ -1203,7 +1203,7 @@ pub mod play {
SpawnExperienceOrb, SpawnExperienceOrb,
SpawnPlayer, SpawnPlayer,
EntityAnimation, EntityAnimation,
AcknoledgeBlockChanges, AcknowledgeBlockChanges,
BlockBreakAnimation, BlockBreakAnimation,
BlockEntityData, BlockEntityData,
BlockAction, BlockAction,
@ -1213,7 +1213,7 @@ pub mod play {
EntityStatus, EntityStatus,
UnloadChunk, UnloadChunk,
ChangeGameState, ChangeGameState,
KeepAliveClientbound, KeepAlive,
ChunkDataAndUpdateLight, ChunkDataAndUpdateLight,
JoinGame, JoinGame,
EntityPosition, EntityPosition,
@ -1223,7 +1223,7 @@ pub mod play {
DestroyEntities, DestroyEntities,
EntityHeadLook, EntityHeadLook,
MultiBlockChange, MultiBlockChange,
HeldItemChangeClientbound, HeldItemChange,
UpdateViewPosition, UpdateViewPosition,
UpdateViewDistance, UpdateViewDistance,
SpawnPosition, SpawnPosition,
@ -1268,7 +1268,7 @@ pub mod play {
} }
def_struct! { def_struct! {
ChatMessageServerbound 0x04 { ChatMessage 0x04 {
message: BoundedString<0, 256> message: BoundedString<0, 256>
// TODO: // TODO:
} }
@ -1337,7 +1337,7 @@ pub mod play {
} }
def_struct! { def_struct! {
TabCompleteServerbound 0x08 { TabComplete 0x08 {
transaction_id: VarInt, transaction_id: VarInt,
/// Text behind the cursor without the '/'. /// Text behind the cursor without the '/'.
text: BoundedString<0, 32500> text: BoundedString<0, 32500>
@ -1358,7 +1358,7 @@ pub mod play {
} }
def_struct! { def_struct! {
PluginMessageServerbound 0x0c { PluginMessage 0x0c {
channel: Ident, channel: Ident,
data: RawBytes, data: RawBytes,
} }
@ -1391,18 +1391,12 @@ pub mod play {
InteractType: VarInt { InteractType: VarInt {
Interact: Hand = 0, Interact: Hand = 0,
Attack = 1, Attack = 1,
InteractAt: InteractAtData = 2 InteractAt: (Vec3<f32>, Hand) = 2
}
}
def_struct! {
InteractAtData {
target: Vec3<f64>,
hand: Hand,
} }
} }
def_enum! { def_enum! {
#[derive(Copy, PartialEq, Eq)]
Hand: VarInt { Hand: VarInt {
Main = 0, Main = 0,
Off = 1, Off = 1,
@ -1418,7 +1412,7 @@ pub mod play {
} }
def_struct! { def_struct! {
KeepAliveServerbound 0x11 { KeepAlive 0x11 {
id: i64, id: i64,
} }
} }
@ -1465,7 +1459,7 @@ pub mod play {
} }
def_struct! { def_struct! {
VehicleMoveServerbound 0x17 { VehicleMove 0x17 {
/// Absolute position /// Absolute position
position: Vec3<f64>, position: Vec3<f64>,
/// Degrees /// Degrees
@ -1497,7 +1491,7 @@ pub mod play {
} }
def_enum! { def_enum! {
PlayerAbilitiesServerbound 0x1b: i8 { PlayerAbilities 0x1b: i8 {
NotFlying = 0, NotFlying = 0,
Flying = 0b10, Flying = 0b10,
} }
@ -1525,6 +1519,7 @@ pub mod play {
} }
def_enum! { def_enum! {
#[derive(Copy, PartialEq, Eq)]
BlockFace: i8 { BlockFace: i8 {
/// -Y /// -Y
Bottom = 0, Bottom = 0,
@ -1644,7 +1639,7 @@ pub mod play {
} }
def_struct! { def_struct! {
HeldItemChangeServerbound 0x27 { HeldItemChange 0x27 {
slot: BoundedInt<i16, 0, 8>, slot: BoundedInt<i16, 0, 8>,
} }
} }
@ -1784,7 +1779,7 @@ pub mod play {
hand: Hand, hand: Hand,
location: BlockPos, location: BlockPos,
face: BlockFace, face: BlockFace,
cursor_pos: Vec3<f64>, cursor_pos: Vec3<f32>,
head_inside_block: bool, head_inside_block: bool,
sequence: VarInt, sequence: VarInt,
} }
@ -1852,29 +1847,29 @@ pub mod play {
QueryBlockNbt, QueryBlockNbt,
SetDifficulty, SetDifficulty,
ChatCommand, ChatCommand,
ChatMessageServerbound, ChatMessage,
ChatPreview, ChatPreview,
ClientStatus, ClientStatus,
ClientSettings, ClientSettings,
TabCompleteServerbound, TabComplete,
ClickWindowButton, ClickWindowButton,
CloseWindow, CloseWindow,
PluginMessageServerbound, PluginMessage,
EditBook, EditBook,
QueryEntityNbt, QueryEntityNbt,
InteractEntity, InteractEntity,
GenerateStructure, GenerateStructure,
KeepAliveServerbound, KeepAlive,
LockDifficulty, LockDifficulty,
PlayerPosition, PlayerPosition,
PlayerPositionAndRotation, PlayerPositionAndRotation,
PlayerRotation, PlayerRotation,
PlayerMovement, PlayerMovement,
VehicleMoveServerbound, VehicleMove,
SteerBoat, SteerBoat,
PickItem, PickItem,
CraftRecipeRequest, CraftRecipeRequest,
PlayerAbilitiesServerbound, PlayerAbilities,
PlayerDigging, PlayerDigging,
EntityAction, EntityAction,
SteerVehicle, SteerVehicle,
@ -1886,7 +1881,7 @@ pub mod play {
AdvancementTab, AdvancementTab,
SelectTrade, SelectTrade,
SetBeaconEffect, SetBeaconEffect,
HeldItemChangeServerbound, HeldItemChange,
UpdateCommandBlock, UpdateCommandBlock,
UpdateCommandBlockMinecart, UpdateCommandBlockMinecart,
CreativeInventoryAction, CreativeInventoryAction,

View file

@ -360,6 +360,13 @@ fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult {
join_player(&server, worlds.reborrow(), msg); 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()); server.config().update(&server, worlds.reborrow());
worlds.par_iter_mut().for_each(|(_, mut world)| { worlds.par_iter_mut().for_each(|(_, mut world)| {

View file

@ -47,6 +47,8 @@ impl SpatialIndex {
}) })
} }
// TODO: accept predicate here. Might want to skip invisible entities, for
// instance.
pub fn raycast(&self, origin: Vec3<f64>, direction: Vec3<f64>) -> Option<RaycastHit> { pub fn raycast(&self, origin: Vec3<f64>, direction: Vec3<f64>) -> Option<RaycastHit> {
debug_assert!( debug_assert!(
direction.is_normalized(), direction.is_normalized(),