From 6c0eef1ae7efda7205787510c8b36053f8b8036a Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Jul 2022 15:51:28 -0700 Subject: [PATCH] Add more client events --- examples/conway.rs | 34 +++++++++- src/client.rs | 140 +++++++++++++++++++++++++++------------- src/client/event.rs | 9 +++ src/entity.rs | 6 +- src/protocol/packets.rs | 8 +-- 5 files changed, 144 insertions(+), 53 deletions(-) diff --git a/examples/conway.rs b/examples/conway.rs index b351e4e..854bbf1 100644 --- a/examples/conway.rs +++ b/examples/conway.rs @@ -7,8 +7,10 @@ use std::sync::Mutex; use log::LevelFilter; use num::Integer; use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; -use valence::client::{ClientId, ClientEvent, GameMode}; +use valence::client::{ClientEvent, ClientId, GameMode}; use valence::config::{Config, ServerListPing}; +use valence::entity::EntityMeta; +use valence::entity::meta::Pose; use valence::text::Color; use valence::{ async_trait, ident, Biome, BlockState, Dimension, DimensionId, EntityId, EntityType, Server, @@ -150,7 +152,7 @@ impl Config for Game { client_id, server .entities - .create_with_uuid(client.uuid(), EntityType::Player) + .create_with_uuid(EntityType::Player, client.uuid()) .unwrap() .0, ); @@ -167,6 +169,7 @@ impl Config for Game { .entities .get_mut(player_entities[&client_id]) .unwrap(); + while let Some(event) = client.pop_event() { match event { ClientEvent::Digging(e) => { @@ -191,6 +194,33 @@ impl Config for Game { player.set_pitch(client.pitch()); player.set_on_ground(client.on_ground()); } + ClientEvent::StartSneaking => { + if let EntityMeta::Player(e) = player.meta_mut() { + e.set_crouching(true); + e.set_pose(Pose::Sneaking); + } + } + ClientEvent::StopSneaking => { + if let EntityMeta::Player(e) = player.meta_mut() { + e.set_pose(Pose::Standing); + e.set_crouching(false); + } + } + ClientEvent::StartSprinting => { + if let EntityMeta::Player(e) = player.meta_mut() { + e.set_sprinting(true); + } + } + ClientEvent::StopSprinting => { + if let EntityMeta::Player(e) = player.meta_mut() { + e.set_sprinting(false); + } + } + ClientEvent::ArmSwing(_) => { + if let EntityMeta::Player(_) = player.meta_mut() { + // TODO: swing arm. + } + } _ => {} } } diff --git a/src/client.rs b/src/client.rs index a7b41a1..d2c7573 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,6 +4,7 @@ use std::collections::{HashSet, VecDeque}; use std::iter::FusedIterator; use std::time::Duration; +use bitfield_struct::bitfield; pub use event::*; use flume::{Receiver, Sender, TrySendError}; use rayon::iter::ParallelIterator; @@ -15,18 +16,20 @@ use crate::dimension::{Dimension, DimensionEffects}; use crate::entity::types::Player; use crate::entity::{velocity_to_packet_units, EntityType}; use crate::player_textures::SignedPlayerTextures; -use crate::protocol::packets::play::c2s::{C2sPlayPacket, DiggingStatus, InteractType}; -pub use crate::protocol::packets::play::s2c::EntityEventStatus as EntityEvent; +use crate::protocol::packets::play::c2s::{ + C2sPlayPacket, DiggingStatus, InteractType, PlayerCommandId, +}; +pub use crate::protocol::packets::play::s2c::EntityEvent; use crate::protocol::packets::play::s2c::{ Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, BlockChangeAck, ChatType, ChatTypeChat, ChatTypeNarration, ChatTypeRegistry, ChatTypeRegistryEntry, DimensionType, - DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, - EntityEvent as EntityEventPacket, ForgetLevelChunk, GameEvent, GameEventReason, KeepAlive, - Login, MoveEntityPosition, MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition, - PlayerPositionFlags, RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket, - SetChunkCacheCenter, SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SpawnPosition, - SystemChat, TeleportEntity, + DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, DoEntityEvent, ForgetLevelChunk, + GameEvent, GameEventReason, KeepAlive, Login, MoveEntityPosition, + MoveEntityPositionAndRotation, MoveEntityRotation, PlayerPosition, PlayerPositionFlags, + RegistryCodec, RemoveEntities, Respawn, RotateHead, S2cPlayPacket, SetChunkCacheCenter, + SetChunkCacheRadius, SetEntityMetadata, SetEntityMotion, SpawnPosition, SystemChat, + TeleportEntity, }; use crate::protocol::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt}; use crate::server::C2sPacketChannels; @@ -106,17 +109,12 @@ pub struct Client { username: String, textures: Option, world: WorldId, - spawn: bool, - on_ground: bool, new_position: Vec3, old_position: Vec3, /// Measured in degrees yaw: f32, /// Measured in degrees pitch: f32, - /// If any of position, yaw, or pitch were modified by the - /// user this tick. - teleported_this_tick: bool, /// Counts up as teleports are made. teleport_id_counter: u32, /// The number of pending client teleports that have yet to receive a @@ -125,14 +123,10 @@ pub struct Client { pending_teleports: u32, spawn_position: BlockPos, spawn_position_yaw: f32, - /// If spawn_position or spawn_position_yaw were modified this tick. - modified_spawn_position: bool, death_location: Option<(DimensionId, BlockPos)>, events: VecDeque, /// The ID of the last keepalive sent. last_keepalive_id: i64, - /// If the last sent keepalive got a response. - got_keepalive: bool, new_max_view_distance: u8, old_max_view_distance: u8, /// Entities that were visible to this client at the end of the last tick. @@ -146,10 +140,27 @@ pub struct Client { dug_blocks: Vec, msgs_to_send: Vec, entity_events: Vec, + flags: ClientFlags, /// The metadata for the client's own player entity. player_meta: Player, } +#[bitfield(u8)] +pub(crate) struct ClientFlags { + 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, + /// If the last sent keepalive got a response. + got_keepalive: bool, +} + impl Client { pub(crate) fn new( packet_channels: C2sPacketChannels, @@ -166,22 +177,17 @@ impl Client { username: ncd.username, textures: ncd.textures, world: WorldId::default(), - spawn: false, - on_ground: false, new_position: Vec3::default(), old_position: Vec3::default(), yaw: 0.0, pitch: 0.0, - teleported_this_tick: false, teleport_id_counter: 0, pending_teleports: 0, spawn_position: BlockPos::default(), spawn_position_yaw: 0.0, - modified_spawn_position: true, death_location: None, events: VecDeque::new(), last_keepalive_id: 0, - got_keepalive: true, new_max_view_distance: 16, old_max_view_distance: 0, loaded_entities: HashSet::new(), @@ -192,6 +198,9 @@ impl Client { dug_blocks: Vec::new(), msgs_to_send: Vec::new(), entity_events: Vec::new(), + flags: ClientFlags::new() + .with_modified_spawn_position(true) + .with_got_keepalive(true), player_meta: Player::new(), } } @@ -218,7 +227,7 @@ impl Client { pub fn spawn(&mut self, world: WorldId) { self.world = world; - self.spawn = true; + self.flags.set_spawn(true); } /// Sends a system message to the player. @@ -236,8 +245,8 @@ impl Client { self.yaw = yaw; self.pitch = pitch; - if !self.teleported_this_tick { - self.teleported_this_tick = true; + 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, @@ -273,7 +282,7 @@ impl Client { if pos != self.spawn_position || yaw_degrees != self.spawn_position_yaw { self.spawn_position = pos; self.spawn_position_yaw = yaw_degrees; - self.modified_spawn_position = true; + self.flags.set_modified_spawn_position(true); } } @@ -305,7 +314,7 @@ impl Client { } pub fn on_ground(&self) -> bool { - self.on_ground + self.flags.on_ground() } pub fn is_disconnected(&self) -> bool { @@ -396,13 +405,13 @@ impl Client { position: client.new_position, yaw: client.yaw, pitch: client.pitch, - on_ground: client.on_ground, + on_ground: client.flags.on_ground(), }; client.new_position = new_position; client.yaw = new_yaw; client.pitch = new_pitch; - client.on_ground = new_on_ground; + client.flags.set_on_ground(new_on_ground); client.events.push_back(event); } @@ -481,7 +490,7 @@ impl Client { C2sPlayPacket::JigsawGenerate(_) => {} C2sPlayPacket::KeepAlive(p) => { let last_keepalive_id = self.last_keepalive_id; - if self.got_keepalive { + if self.flags.got_keepalive() { log::warn!("unexpected keepalive from player {}", self.username()); self.disconnect_no_reason(); } else if p.id != last_keepalive_id { @@ -493,7 +502,7 @@ impl Client { ); self.disconnect_no_reason(); } else { - self.got_keepalive = true; + self.flags.set_got_keepalive(true); } } C2sPlayPacket::LockDifficulty(_) => {} @@ -515,7 +524,14 @@ impl Client { p.on_ground, ), C2sPlayPacket::MoveVehicle(p) => { - handle_movement_packet(self, true, p.position, p.yaw, p.pitch, self.on_ground); + handle_movement_packet( + self, + true, + p.position, + p.yaw, + p.pitch, + self.flags.on_ground(), + ); } C2sPlayPacket::PaddleBoat(p) => { self.events.push_back(ClientEvent::SteerBoat { @@ -556,7 +572,43 @@ impl Client { DiggingStatus::SwapItemInHand => return, }); } - C2sPlayPacket::PlayerCommand(_) => {} + 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. + self.events.push_back(match e.action_id { + PlayerCommandId::StartSneaking => { + self.flags.set_sneaking(true); + ClientEvent::StartSneaking + } + PlayerCommandId::StopSneaking => { + self.flags.set_sneaking(false); + ClientEvent::StopSneaking + } + PlayerCommandId::LeaveBed => ClientEvent::LeaveBed, + PlayerCommandId::StartSprinting => { + self.flags.set_sprinting(true); + ClientEvent::StartSprinting + } + PlayerCommandId::StopSprinting => { + self.flags.set_sprinting(false); + ClientEvent::StopSprinting + } + PlayerCommandId::StartJumpWithHorse => { + self.flags.set_jumping_with_horse(true); + ClientEvent::StartJumpWithHorse(e.jump_boost.0 .0 as u8) + } + PlayerCommandId::StopJumpWithHorse => { + self.flags.set_jumping_with_horse(false); + ClientEvent::StopJumpWithHorse + } + PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory, + PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra, + }); + } C2sPlayPacket::PlayerInput(_) => {} C2sPlayPacket::Pong(_) => {} C2sPlayPacket::RecipeBookChangeSettings(_) => {} @@ -646,7 +698,7 @@ impl Client { self.teleport(self.position(), self.yaw(), self.pitch()); } else { - if self.spawn { + if self.flags.spawn() { self.loaded_entities.clear(); self.loaded_chunks.clear(); @@ -703,8 +755,8 @@ impl Client { } // Update the players spawn position (compass position) - if self.modified_spawn_position { - self.modified_spawn_position = false; + if self.flags.modified_spawn_position() { + self.flags.set_modified_spawn_position(false); self.send_packet(SpawnPosition { location: self.spawn_position, @@ -724,11 +776,11 @@ impl Client { // Check if it's time to send another keepalive. if current_tick % (shared.tick_rate() * 8) == 0 { - if self.got_keepalive { + if self.flags.got_keepalive() { let id = rand::random(); self.send_packet(KeepAlive { id }); self.last_keepalive_id = id; - self.got_keepalive = false; + self.flags.set_got_keepalive(false); } else { log::warn!( "player {} timed out (no keepalive response)", @@ -808,8 +860,8 @@ impl Client { // This is done after the chunks are loaded so that the "downloading terrain" // screen is closed at the appropriate time. - if self.teleported_this_tick { - self.teleported_this_tick = false; + if self.flags.teleported_this_tick() { + self.flags.set_teleported_this_tick(false); self.send_packet(PlayerPosition { position: self.new_position, @@ -922,7 +974,7 @@ impl Client { for &e in entity.events() { send_packet( &mut self.send, - EntityEventPacket { + DoEntityEvent { entity_id: id.to_network_id(), entity_status: e, }, @@ -982,7 +1034,7 @@ impl Client { for &e in entity.events() { send_packet( &mut self.send, - EntityEventPacket { + DoEntityEvent { entity_id: id.to_network_id(), entity_status: e, }, @@ -997,7 +1049,7 @@ impl Client { for event in self.entity_events.drain(..) { send_packet( &mut self.send, - EntityEventPacket { + DoEntityEvent { entity_id: 0, entity_status: event, }, @@ -1005,7 +1057,7 @@ impl Client { } self.old_position = self.new_position; - self.spawn = false; + self.flags.set_spawn(false); } } diff --git a/src/client/event.rs b/src/client/event.rs index 340484e..74aaba4 100644 --- a/src/client/event.rs +++ b/src/client/event.rs @@ -24,6 +24,15 @@ pub enum ClientEvent { pitch: f32, on_ground: bool, }, + StartSneaking, + StopSneaking, + StartSprinting, + StopSprinting, + StartJumpWithHorse(u8), + StopJumpWithHorse, + LeaveBed, + OpenHorseInventory, + StartFlyingWithElytra, ArmSwing(Hand), InteractWithEntity { /// The ID of the entity being interacted with. diff --git a/src/entity.rs b/src/entity.rs index 1eb9b4d..43a20e7 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -12,7 +12,7 @@ pub use types::{EntityMeta, EntityType}; use uuid::Uuid; use vek::{Aabb, Vec3}; -pub use crate::protocol::packets::play::s2c::EntityEventStatus as EntityEvent; +pub use crate::protocol::packets::play::s2c::EntityEvent as EntityEvent; use crate::protocol::packets::play::s2c::{ AddEntity, AddExperienceOrb, AddPlayer, S2cPlayPacket, SetEntityMetadata, }; @@ -39,7 +39,7 @@ impl Entities { /// Spawns a new entity with the default data. The new entity's [`EntityId`] /// is returned. pub fn create(&mut self, typ: EntityType) -> (EntityId, &mut Entity) { - self.create_with_uuid(Uuid::from_bytes(rand::random()), typ) + self.create_with_uuid(typ, Uuid::from_bytes(rand::random())) .expect("UUID collision") } @@ -50,8 +50,8 @@ impl Entities { /// world. If it does, `None` is returned and the entity is not spawned. pub fn create_with_uuid( &mut self, - uuid: Uuid, typ: EntityType, + uuid: Uuid, ) -> Option<(EntityId, &mut Entity)> { match self.uuid_to_entity.entry(uuid) { Entry::Occupied(_) => None, diff --git a/src/protocol/packets.rs b/src/protocol/packets.rs index 1170e17..16b2a33 100644 --- a/src/protocol/packets.rs +++ b/src/protocol/packets.rs @@ -742,15 +742,15 @@ pub mod play { } def_struct! { - EntityEvent 0x18 { + DoEntityEvent 0x18 { entity_id: i32, - entity_status: EntityEventStatus, + entity_status: EntityEvent, } } def_enum! { #[derive(Copy, PartialEq, Eq)] - EntityEventStatus: u8 { + EntityEvent: u8 { Jump = 1, Hurt = 2, Death = 3, @@ -1294,7 +1294,7 @@ pub mod play { BlockUpdate, BossEvent, Disconnect, - EntityEvent, + DoEntityEvent, ForgetLevelChunk, GameEvent, KeepAlive,