From efc28739081094686e0233c8f613ff2e46932e1d Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 9 Jun 2022 20:26:21 -0700 Subject: [PATCH] Reorganize modules --- src/biome.rs | 118 ++ src/byte_angle.rs | 6 +- src/chunk.rs | 7 +- src/client.rs | 128 +- src/config.rs | 202 +-- src/dimension.rs | 82 + src/entity.rs | 6 +- src/{identifier.rs => ident.rs} | 56 +- src/lib.rs | 10 +- src/packets.rs | 2773 ++++++++++++++++--------------- src/server.rs | 38 +- src/slotmap.rs | 2 +- src/text.rs | 6 +- src/util.rs | 12 +- src/world.rs | 3 +- 15 files changed, 1729 insertions(+), 1720 deletions(-) create mode 100644 src/biome.rs create mode 100644 src/dimension.rs rename src/{identifier.rs => ident.rs} (86%) diff --git a/src/biome.rs b/src/biome.rs new file mode 100644 index 0000000..fea6234 --- /dev/null +++ b/src/biome.rs @@ -0,0 +1,118 @@ +use crate::{ident, Ident}; + +/// Identifies a particular [`Biome`]. +/// +/// Biome IDs are always valid and are cheap to copy and store. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct BiomeId(pub(crate) u16); + +impl BiomeId { + pub fn to_index(self) -> usize { + self.0 as usize + } +} + +/// Contains the configuration for a biome. +#[derive(Clone, Debug)] +pub struct Biome { + /// The unique name for this biome. The name can be + /// seen in the F3 debug menu. + pub name: Ident, + pub precipitation: BiomePrecipitation, + pub sky_color: u32, + pub water_fog_color: u32, + pub fog_color: u32, + pub water_color: u32, + pub foliage_color: Option, + pub grass_color_modifier: BiomeGrassColorModifier, + pub music: Option, + pub ambient_sound: Option, + pub additions_sound: Option, + pub mood_sound: Option, + pub particle: Option, + // TODO: The following fields should be added if they can affect the appearance of the biome to + // clients. + // * depth: f32 + // * temperature: f32 + // * scale: f32 + // * downfall: f32 + // * category + // * temperature_modifier + // * grass_color (misleading name?) +} + +impl Default for Biome { + fn default() -> Self { + Self { + name: ident!("plains"), + precipitation: BiomePrecipitation::Rain, + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + foliage_color: None, + grass_color_modifier: BiomeGrassColorModifier::None, + music: None, + ambient_sound: None, + additions_sound: None, + mood_sound: None, + particle: None, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum BiomePrecipitation { + Rain, + Snow, + None, +} + +impl Default for BiomePrecipitation { + fn default() -> Self { + Self::Rain + } +} + +/// Minecraft handles grass colors for swamps and dark oak forests in a special +/// way. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum BiomeGrassColorModifier { + Swamp, + DarkForest, + None, +} + +impl Default for BiomeGrassColorModifier { + fn default() -> Self { + Self::None + } +} + +#[derive(Clone, Debug)] +pub struct BiomeMusic { + pub replace_current_music: bool, + pub sound: Ident, + pub min_delay: i32, + pub max_delay: i32, +} + +#[derive(Clone, Debug)] +pub struct BiomeAdditionsSound { + pub sound: Ident, + pub tick_chance: f64, +} + +#[derive(Clone, Debug)] +pub struct BiomeMoodSound { + pub sound: Ident, + pub tick_delay: i32, + pub offset: f64, + pub block_search_extent: i32, +} + +#[derive(Clone, Debug)] +pub struct BiomeParticle { + pub probability: f32, + pub typ: Ident, +} diff --git a/src/byte_angle.rs b/src/byte_angle.rs index 741bc7b..a3c78f5 100644 --- a/src/byte_angle.rs +++ b/src/byte_angle.rs @@ -11,9 +11,9 @@ impl ByteAngle { ByteAngle((f.rem_euclid(360.0) / 360.0 * 256.0).round() as u8) } - pub fn to_degrees(self) -> f32 { - self.0 as f32 / 256.0 * 360.0 - } + // pub fn to_degrees(self) -> f32 { + // self.0 as f32 / 256.0 * 360.0 + // } } impl Encode for ByteAngle { diff --git a/src/chunk.rs b/src/chunk.rs index 94a80ea..9ea8876 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -8,11 +8,10 @@ use std::ops::Deref; use bitvec::vec::BitVec; use num::Integer; use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; -use vek::Vec2; use crate::block::BlockState; -use crate::packets::play::{ - BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange, +use crate::packets::play::s2c::{ + BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, MultiBlockChange, S2cPlayPacket, }; use crate::protocol::{Encode, Nbt}; use crate::var_int::VarInt; @@ -269,7 +268,7 @@ pub(crate) enum BlockChangePacket { Multi(MultiBlockChange), } -impl From for ClientPlayPacket { +impl From for S2cPlayPacket { fn from(p: BlockChangePacket) -> Self { match p { BlockChangePacket::Single(p) => p.into(), diff --git a/src/client.rs b/src/client.rs index 575b68a..8c3078a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,24 +7,24 @@ use rayon::iter::ParallelIterator; use uuid::Uuid; use vek::Vec3; +use crate::biome::{Biome, BiomeGrassColorModifier, BiomePrecipitation}; use crate::block_pos::BlockPos; use crate::byte_angle::ByteAngle; -use crate::config::{ - Biome, BiomeGrassColorModifier, BiomePrecipitation, Dimension, DimensionEffects, DimensionId, -}; +use crate::dimension::{Dimension, DimensionEffects}; use crate::entity::{velocity_to_packet_units, EntityType}; -pub use crate::packets::play::GameMode; -use crate::packets::play::{ +use crate::packets::play::c2s::C2sPlayPacket; +pub use crate::packets::play::s2c::GameMode; +use crate::packets::play::s2c::{ Biome as BiomeRegistryBiome, BiomeAdditionsSound, BiomeEffects, BiomeMoodSound, BiomeMusic, BiomeParticle, BiomeParticleOptions, BiomeProperty, BiomeRegistry, ChangeGameState, - ChangeGameStateReason, ClientPlayPacket, DestroyEntities, DimensionCodec, DimensionType, - DimensionTypeRegistry, DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition, + ChangeGameStateReason, DestroyEntities, DimensionCodec, DimensionType, DimensionTypeRegistry, + DimensionTypeRegistryEntry, Disconnect, EntityHeadLook, EntityPosition, EntityPositionAndRotation, EntityRotation, EntityTeleport, EntityVelocity, JoinGame, - KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, ServerPlayPacket, + KeepAliveClientbound, PlayerPositionAndLook, PlayerPositionAndLookFlags, S2cPlayPacket, SpawnPosition, UnloadChunk, UpdateViewDistance, UpdateViewPosition, }; use crate::protocol::{BoundedInt, Nbt}; -use crate::server::ServerPacketChannels; +use crate::server::C2sPacketChannels; use crate::slotmap::{Key, SlotMap}; use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance}; use crate::var_int::VarInt; @@ -113,8 +113,8 @@ pub struct ClientId(Key); /// Represents a client connected to the server after logging in. pub struct Client { /// Setting this to `None` disconnects the client. - send: Option>, - recv: Receiver, + send: Option>, + recv: Receiver, /// The tick this client was created. created_tick: Ticks, username: String, @@ -170,7 +170,7 @@ impl<'a> Deref for ClientMut<'a> { impl Client { pub(crate) fn new( - packet_channels: ServerPacketChannels, + packet_channels: C2sPacketChannels, username: String, uuid: Uuid, server: &Server, @@ -301,7 +301,7 @@ impl<'a> ClientMut<'a> { /// Attempts to enqueue a play packet to be sent to this client. The client /// is disconnected if the clientbound packet buffer is full. - pub(crate) fn send_packet(&mut self, packet: impl Into) { + pub(crate) fn send_packet(&mut self, packet: impl Into) { send_packet(&mut self.0.send, packet); } @@ -630,7 +630,7 @@ impl<'a> ClientMut<'a> { self.0.old_position = self.0.new_position; } - fn handle_serverbound_packet(&mut self, pkt: ServerPlayPacket) { + fn handle_serverbound_packet(&mut self, pkt: C2sPlayPacket) { let client = &mut self.0; fn handle_movement_packet( @@ -658,7 +658,7 @@ impl<'a> ClientMut<'a> { } match pkt { - ServerPlayPacket::TeleportConfirm(p) => { + C2sPlayPacket::TeleportConfirm(p) => { if client.pending_teleports == 0 { self.disconnect("Unexpected teleport confirmation"); return; @@ -677,11 +677,11 @@ impl<'a> ClientMut<'a> { )); } } - ServerPlayPacket::QueryBlockNbt(_) => {} - ServerPlayPacket::SetDifficulty(_) => {} - ServerPlayPacket::ChatMessageServerbound(_) => {} - ServerPlayPacket::ClientStatus(_) => {} - ServerPlayPacket::ClientSettings(p) => { + C2sPlayPacket::QueryBlockNbt(_) => {} + C2sPlayPacket::SetDifficulty(_) => {} + C2sPlayPacket::ChatMessageServerbound(_) => {} + C2sPlayPacket::ClientStatus(_) => {} + C2sPlayPacket::ClientSettings(p) => { let old = client.settings.replace(Settings { locale: p.locale.0, view_distance: p.view_distance.0, @@ -694,16 +694,16 @@ impl<'a> ClientMut<'a> { client.events.push(Event::SettingsChanged(old)); } - ServerPlayPacket::TabCompleteServerbound(_) => {} - ServerPlayPacket::ClickWindowButton(_) => {} - ServerPlayPacket::ClickWindow(_) => {} - ServerPlayPacket::CloseWindow(_) => {} - ServerPlayPacket::PluginMessageServerbound(_) => {} - ServerPlayPacket::EditBook(_) => {} - ServerPlayPacket::QueryEntityNbt(_) => {} - ServerPlayPacket::InteractEntity(_) => {} - ServerPlayPacket::GenerateStructure(_) => {} - ServerPlayPacket::KeepAliveServerbound(p) => { + C2sPlayPacket::TabCompleteServerbound(_) => {} + C2sPlayPacket::ClickWindowButton(_) => {} + C2sPlayPacket::ClickWindow(_) => {} + 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"); @@ -716,51 +716,51 @@ impl<'a> ClientMut<'a> { client.got_keepalive = true; } } - ServerPlayPacket::LockDifficulty(_) => {} - ServerPlayPacket::PlayerPosition(p) => { + C2sPlayPacket::LockDifficulty(_) => {} + C2sPlayPacket::PlayerPosition(p) => { handle_movement_packet(client, p.position, client.yaw, client.pitch, p.on_ground) } - ServerPlayPacket::PlayerPositionAndRotation(p) => { + C2sPlayPacket::PlayerPositionAndRotation(p) => { handle_movement_packet(client, p.position, p.yaw, p.pitch, p.on_ground) } - ServerPlayPacket::PlayerRotation(p) => { + C2sPlayPacket::PlayerRotation(p) => { handle_movement_packet(client, client.new_position, p.yaw, p.pitch, p.on_ground) } - ServerPlayPacket::PlayerMovement(p) => handle_movement_packet( + C2sPlayPacket::PlayerMovement(p) => handle_movement_packet( client, client.new_position, client.yaw, client.pitch, p.on_ground, ), - ServerPlayPacket::VehicleMoveServerbound(_) => {} - ServerPlayPacket::SteerBoat(_) => {} - ServerPlayPacket::PickItem(_) => {} - ServerPlayPacket::CraftRecipeRequest(_) => {} - ServerPlayPacket::PlayerAbilitiesServerbound(_) => {} - ServerPlayPacket::PlayerDigging(_) => {} - ServerPlayPacket::EntityAction(_) => {} - ServerPlayPacket::SteerVehicle(_) => {} - ServerPlayPacket::Pong(_) => {} - ServerPlayPacket::SetRecipeBookState(_) => {} - ServerPlayPacket::SetDisplayedRecipe(_) => {} - ServerPlayPacket::NameItem(_) => {} - ServerPlayPacket::ResourcePackStatus(_) => {} - ServerPlayPacket::AdvancementTab(_) => {} - ServerPlayPacket::SelectTrade(_) => {} - ServerPlayPacket::SetBeaconEffect(_) => {} - ServerPlayPacket::HeldItemChangeServerbound(_) => {} - ServerPlayPacket::UpdateCommandBlock(_) => {} - ServerPlayPacket::UpdateCommandBlockMinecart(_) => {} - ServerPlayPacket::CreativeInventoryAction(_) => {} - ServerPlayPacket::UpdateJigsawBlock(_) => {} - ServerPlayPacket::UpdateStructureBlock(_) => {} - ServerPlayPacket::UpdateSign(_) => {} - ServerPlayPacket::PlayerArmSwing(_) => {} - ServerPlayPacket::Spectate(_) => {} - ServerPlayPacket::PlayerBlockPlacement(_) => {} - ServerPlayPacket::UseItem(_) => {} + 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(_) => {} } } } @@ -803,9 +803,9 @@ pub struct Settings { pub allow_server_listings: bool, } -pub use crate::packets::play::{ChatMode, DisplayedSkinParts, MainHand}; +pub use crate::packets::play::c2s::{ChatMode, DisplayedSkinParts, MainHand}; -fn send_packet(send_opt: &mut Option>, pkt: impl Into) { +fn send_packet(send_opt: &mut Option>, pkt: impl Into) { if let Some(send) = send_opt { match send.try_send(pkt.into()) { Err(TrySendError::Full(_)) => { diff --git a/src/config.rs b/src/config.rs index 2d4db76..3ba25b8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use tokio::runtime::Handle as TokioHandle; use crate::client::ClientMut; -use crate::{ident, Identifier, NewClientData, Server, Text, Ticks, WorldId, WorldsMut}; +use crate::{Biome, Dimension, NewClientData, Server, Text, Ticks, WorldId, WorldsMut}; /// A trait containing callbacks which are invoked by the running Minecraft /// server. @@ -223,203 +223,3 @@ pub enum ServerListPing<'a> { /// Ignores the query and disconnects from the client. Ignore, } - -/// A handle to a particular [`Dimension`] on the server. -/// -/// Dimension IDs must only be used on servers from which they originate. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct DimensionId(pub(crate) u16); - -impl DimensionId { - pub fn to_index(self) -> usize { - self.0 as usize - } -} - -/// The default dimension ID corresponds to the first element in the `Vec` -/// returned by [`Config::dimensions`]. -impl Default for DimensionId { - fn default() -> Self { - Self(0) - } -} - -/// Contains the configuration for a dimension type. -/// -/// In Minecraft, "dimension" and "dimension type" are two different concepts. -/// For instance, the Overworld and Nether are dimensions, each with -/// their own dimension type. A dimension in this library is analogous to a -/// [`World`](crate::World) while [`Dimension`] represents a -/// dimension type. -#[derive(Clone, Debug)] -pub struct Dimension { - /// When false, compases will spin randomly. - pub natural: bool, - /// Must be between 0.0 and 1.0. - pub ambient_light: f32, - /// Must be between 0 and 24000. - pub fixed_time: Option, - /// Determines what skybox/fog effects to use. - pub effects: DimensionEffects, - /// The minimum height in which blocks can exist in this dimension. - /// - /// `min_y` must meet the following conditions: - /// * `min_y % 16 == 0` - /// * `-2032 <= min_y <= 2016` - pub min_y: i32, - /// The total height in which blocks can exist in this dimension. - /// - /// `height` must meet the following conditions: - /// * `height % 16 == 0` - /// * `0 <= height <= 4064` - /// * `min_y + height <= 2032` - pub height: i32, - // TODO: The following fields should be added if they can affect the - // appearance of the dimension to clients. - // * infiniburn - // * respawn_anchor_works - // * has_skylight - // * bed_works - // * has_raids - // * logical_height - // * coordinate_scale - // * ultrawarm - // * has_ceiling -} - -impl Default for Dimension { - fn default() -> Self { - Self { - natural: true, - ambient_light: 1.0, - fixed_time: None, - effects: DimensionEffects::Overworld, - min_y: -64, - height: 384, - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum DimensionEffects { - Overworld, - TheNether, - TheEnd, -} - -/// Identifies a particular [`Biome`]. -/// -/// Biome IDs are always valid and are cheap to copy and store. -#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct BiomeId(pub(crate) u16); - -impl BiomeId { - pub fn to_index(self) -> usize { - self.0 as usize - } -} - -/// Contains the configuration for a biome. -#[derive(Clone, Debug)] -pub struct Biome { - /// The unique name for this biome. The name can be - /// seen in the F3 debug menu. - pub name: Identifier, - pub precipitation: BiomePrecipitation, - pub sky_color: u32, - pub water_fog_color: u32, - pub fog_color: u32, - pub water_color: u32, - pub foliage_color: Option, - pub grass_color_modifier: BiomeGrassColorModifier, - pub music: Option, - pub ambient_sound: Option, - pub additions_sound: Option, - pub mood_sound: Option, - pub particle: Option, - // TODO: The following fields should be added if they can affect the appearance of the biome to - // clients. - // * depth: f32 - // * temperature: f32 - // * scale: f32 - // * downfall: f32 - // * category - // * temperature_modifier - // * grass_color (misleading name?) -} - -impl Default for Biome { - fn default() -> Self { - Self { - name: ident!("plains"), - precipitation: BiomePrecipitation::Rain, - sky_color: 7907327, - water_fog_color: 329011, - fog_color: 12638463, - water_color: 4159204, - foliage_color: None, - grass_color_modifier: BiomeGrassColorModifier::None, - music: None, - ambient_sound: None, - additions_sound: None, - mood_sound: None, - particle: None, - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum BiomePrecipitation { - Rain, - Snow, - None, -} - -impl Default for BiomePrecipitation { - fn default() -> Self { - Self::Rain - } -} - -/// Minecraft handles grass colors for swamps and dark oak forests in a special -/// way. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum BiomeGrassColorModifier { - Swamp, - DarkForest, - None, -} - -impl Default for BiomeGrassColorModifier { - fn default() -> Self { - Self::None - } -} - -#[derive(Clone, Debug)] -pub struct BiomeMusic { - pub replace_current_music: bool, - pub sound: Identifier, - pub min_delay: i32, - pub max_delay: i32, -} - -#[derive(Clone, Debug)] -pub struct BiomeAdditionsSound { - pub sound: Identifier, - pub tick_chance: f64, -} - -#[derive(Clone, Debug)] -pub struct BiomeMoodSound { - pub sound: Identifier, - pub tick_delay: i32, - pub offset: f64, - pub block_search_extent: i32, -} - -#[derive(Clone, Debug)] -pub struct BiomeParticle { - pub probability: f32, - pub typ: Identifier, -} diff --git a/src/dimension.rs b/src/dimension.rs new file mode 100644 index 0000000..4b64728 --- /dev/null +++ b/src/dimension.rs @@ -0,0 +1,82 @@ +/// A handle to a particular [`Dimension`] on the server. +/// +/// Dimension IDs must only be used on servers from which they originate. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct DimensionId(pub(crate) u16); + +impl DimensionId { + pub fn to_index(self) -> usize { + self.0 as usize + } +} + +/// The default dimension ID corresponds to the first element in the `Vec` +/// returned by [`Config::dimensions`]. +impl Default for DimensionId { + fn default() -> Self { + Self(0) + } +} + +/// Contains the configuration for a dimension type. +/// +/// In Minecraft, "dimension" and "dimension type" are two different concepts. +/// For instance, the Overworld and Nether are dimensions, each with +/// their own dimension type. A dimension in this library is analogous to a +/// [`World`](crate::World) while [`Dimension`] represents a +/// dimension type. +#[derive(Clone, Debug)] +pub struct Dimension { + /// When false, compases will spin randomly. + pub natural: bool, + /// Must be between 0.0 and 1.0. + pub ambient_light: f32, + /// Must be between 0 and 24000. + pub fixed_time: Option, + /// Determines what skybox/fog effects to use. + pub effects: DimensionEffects, + /// The minimum height in which blocks can exist in this dimension. + /// + /// `min_y` must meet the following conditions: + /// * `min_y % 16 == 0` + /// * `-2032 <= min_y <= 2016` + pub min_y: i32, + /// The total height in which blocks can exist in this dimension. + /// + /// `height` must meet the following conditions: + /// * `height % 16 == 0` + /// * `0 <= height <= 4064` + /// * `min_y + height <= 2032` + pub height: i32, + // TODO: The following fields should be added if they can affect the + // appearance of the dimension to clients. + // * infiniburn + // * respawn_anchor_works + // * has_skylight + // * bed_works + // * has_raids + // * logical_height + // * coordinate_scale + // * ultrawarm + // * has_ceiling +} + +impl Default for Dimension { + fn default() -> Self { + Self { + natural: true, + ambient_light: 1.0, + fixed_time: None, + effects: DimensionEffects::Overworld, + min_y: -64, + height: 384, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DimensionEffects { + Overworld, + TheNether, + TheEnd, +} diff --git a/src/entity.rs b/src/entity.rs index 07421df..c593195 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -12,8 +12,8 @@ use uuid::Uuid; use vek::{Aabb, Vec3}; use crate::byte_angle::ByteAngle; -use crate::packets::play::{ - ClientPlayPacket, EntityMetadata, SpawnEntity, SpawnExperienceOrb, SpawnLivingEntity, +use crate::packets::play::s2c::{ + EntityMetadata, S2cPlayPacket, SpawnEntity, SpawnExperienceOrb, SpawnLivingEntity, SpawnPainting, SpawnPlayer, }; use crate::protocol::RawBytes; @@ -580,7 +580,7 @@ pub(crate) enum EntitySpawnPacket { SpawnPlayer(SpawnPlayer), } -impl From for ClientPlayPacket { +impl From for S2cPlayPacket { fn from(pkt: EntitySpawnPacket) -> Self { match pkt { EntitySpawnPacket::SpawnEntity(pkt) => pkt.into(), diff --git a/src/identifier.rs b/src/ident.rs similarity index 86% rename from src/identifier.rs rename to src/ident.rs index b14aaa9..9612679 100644 --- a/src/identifier.rs +++ b/src/ident.rs @@ -17,7 +17,7 @@ use crate::protocol::{encode_string_bounded, BoundedString, Decode, Encode}; /// /// The entire identifier must match the regex `([a-z0-9_-]+:)?[a-z0-9_\/.-]+`. #[derive(Clone, Eq)] -pub struct Identifier { +pub struct Ident { ident: Cow<'static, AsciiStr>, /// The index of the ':' character in the string. /// If there is no namespace then it is `usize::MAX`. @@ -33,12 +33,12 @@ pub struct ParseError { src: Cow<'static, str>, } -impl Identifier { +impl Ident { /// Parses a new identifier from a string. /// /// The string must match the regex `([a-z0-9_-]+:)?[a-z0-9_\/.-]+`. /// If not, an error is returned. - pub fn new(str: impl Into>) -> Result { + pub fn new(str: impl Into>) -> Result { #![allow(bindings_with_variant_name)] let cow = match str.into() { @@ -125,55 +125,55 @@ impl ParseError { } } -impl std::fmt::Debug for Identifier { +impl std::fmt::Debug for Ident { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Identifier").field(&self.as_str()).finish() } } -impl FromStr for Identifier { +impl FromStr for Ident { type Err = ParseError; fn from_str(s: &str) -> Result { - Identifier::new(s.to_string()) + Ident::new(s.to_string()) } } -impl From for String { - fn from(id: Identifier) -> Self { +impl From for String { + fn from(id: Ident) -> Self { id.ident.into_owned().into() } } -impl From for Cow<'static, str> { - fn from(id: Identifier) -> Self { +impl From for Cow<'static, str> { + fn from(id: Ident) -> Self { ascii_cow_to_str_cow(id.ident) } } -impl AsRef for Identifier { +impl AsRef for Ident { fn as_ref(&self) -> &str { self.as_str() } } -impl TryFrom for Identifier { +impl TryFrom for Ident { type Error = ParseError; fn try_from(value: String) -> Result { - Identifier::new(value) + Ident::new(value) } } -impl TryFrom<&'static str> for Identifier { +impl TryFrom<&'static str> for Ident { type Error = ParseError; fn try_from(value: &'static str) -> Result { - Identifier::new(value) + Ident::new(value) } } -impl std::fmt::Display for Identifier { +impl std::fmt::Display for Ident { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.as_str()) } @@ -181,40 +181,40 @@ impl std::fmt::Display for Identifier { /// Equality for identifiers respects the fact that "minecraft:apple" and /// "apple" have the same meaning. -impl PartialEq for Identifier { +impl PartialEq for Ident { fn eq(&self, other: &Self) -> bool { self.namespace().unwrap_or("minecraft") == other.namespace().unwrap_or("minecraft") && self.name() == other.name() } } -impl std::hash::Hash for Identifier { +impl std::hash::Hash for Ident { fn hash(&self, state: &mut H) { self.namespace().unwrap_or("minecraft").hash(state); self.name().hash(state); } } -impl Encode for Identifier { +impl Encode for Ident { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { encode_string_bounded(self.as_str(), 0, 32767, w) } } -impl Decode for Identifier { +impl Decode for Ident { fn decode(r: &mut impl Read) -> anyhow::Result { let string = BoundedString::<0, 32767>::decode(r)?.0; - Ok(Identifier::new(string)?) + Ok(Ident::new(string)?) } } -impl Serialize for Identifier { +impl Serialize for Ident { fn serialize(&self, serializer: S) -> Result { self.as_str().serialize(serializer) } } -impl<'de> Deserialize<'de> for Identifier { +impl<'de> Deserialize<'de> for Ident { fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_str(IdentifierVisitor) } @@ -224,18 +224,18 @@ impl<'de> Deserialize<'de> for Identifier { struct IdentifierVisitor; impl<'de> Visitor<'de> for IdentifierVisitor { - type Value = Identifier; + type Value = Ident; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "a valid Minecraft identifier") } fn visit_str(self, s: &str) -> Result { - Identifier::from_str(s).map_err(E::custom) + Ident::from_str(s).map_err(E::custom) } fn visit_string(self, s: String) -> Result { - Identifier::new(s).map_err(E::custom) + Ident::new(s).map_err(E::custom) } } @@ -249,8 +249,8 @@ macro_rules! ident { let errmsg = "invalid identifier in `ident` macro"; #[allow(clippy::redundant_closure_call)] (|args: ::std::fmt::Arguments| match args.as_str() { - Some(s) => $crate::Identifier::new(s).expect(errmsg), - None => $crate::Identifier::new(args.to_string()).expect(errmsg), + Some(s) => $crate::Ident::new(s).expect(errmsg), + None => $crate::Ident::new(args.to_string()).expect(errmsg), })(format_args!($($arg)*)) }} } diff --git a/src/lib.rs b/src/lib.rs index e6b8a18..a1e6d57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ // missing_docs )] +pub mod biome; pub mod block; mod block_pos; mod byte_angle; @@ -14,8 +15,9 @@ pub mod chunk; pub mod client; mod codec; pub mod config; +pub mod dimension; pub mod entity; -pub mod identifier; +pub mod ident; mod packets; mod protocol; pub mod server; @@ -27,12 +29,14 @@ mod var_long; pub mod world; pub use async_trait::async_trait; +pub use biome::{Biome, BiomeId}; pub use block_pos::BlockPos; pub use chunk::{Chunk, ChunkPos, Chunks, ChunksMut}; pub use client::{Client, ClientMut, Clients, ClientsMut}; -pub use config::{Biome, BiomeId, Config, Dimension, DimensionId}; +pub use config::Config; +pub use dimension::{Dimension, DimensionId}; pub use entity::{Entities, EntitiesMut, Entity, EntityId, EntityType}; -pub use identifier::Identifier; +pub use ident::Ident; pub use server::{start_server, NewClientData, Server, ShutdownResult}; pub use text::{Text, TextFormat}; pub use uuid::Uuid; diff --git a/src/packets.rs b/src/packets.rs index c31174d..96c4509 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -17,11 +17,10 @@ use vek::Vec3; use crate::block_pos::BlockPos; use crate::byte_angle::ByteAngle; -use crate::identifier::Identifier; use crate::protocol::{BoundedArray, BoundedInt, BoundedString, Decode, Encode, Nbt, RawBytes}; use crate::var_int::VarInt; use crate::var_long::VarLong; -use crate::Text; +use crate::{Ident, Text}; /// Trait for types that can be written to the Minecraft protocol as a complete /// packet. @@ -353,1413 +352,1418 @@ pub mod handshake { /// Packets and types used during the status state. pub mod status { - use super::*; + pub mod s2c { + use super::super::*; - // ==== Clientbound ==== + def_struct! { + Response 0x00 { + json_response: String + } + } - def_struct! { - Response 0x00 { - json_response: String + def_struct! { + Pong 0x01 { + /// Should be the same as the payload from [`Ping`]. + payload: u64 + } } } - def_struct! { - Pong 0x01 { - /// Should be the same as the payload from [`Ping`]. - payload: u64 + pub mod c2s { + use super::super::*; + + def_struct! { + Request 0x00 {} } - } - // ==== Serverbound ==== - - def_struct! { - Request 0x00 {} - } - - def_struct! { - Ping 0x01 { - payload: u64 + def_struct! { + Ping 0x01 { + payload: u64 + } } } } /// Packets and types used during the play state. pub mod login { - use super::*; + pub mod s2c { + use super::super::*; - // ==== Clientbound ==== + def_struct! { + Disconnect 0x00 { + reason: Text, + } + } - def_struct! { - Disconnect 0x00 { - reason: Text, + def_struct! { + EncryptionRequest 0x01 { + /// Currently unused + server_id: BoundedString<0, 20>, + /// The RSA public key + public_key: Vec, + verify_token: BoundedArray, + } + } + + def_struct! { + LoginSuccess 0x02 { + uuid: Uuid, + username: BoundedString<3, 16>, + } + } + + def_struct! { + SetCompression 0x03 { + threshold: VarInt + } } } - def_struct! { - EncryptionRequest 0x01 { - /// Currently unused - server_id: BoundedString<0, 20>, - /// The RSA public key - public_key: Vec, - verify_token: BoundedArray, + pub mod c2s { + use super::super::*; + + def_struct! { + LoginStart 0x00 { + username: BoundedString<3, 16>, + } } - } - def_struct! { - LoginSuccess 0x02 { - uuid: Uuid, - username: BoundedString<3, 16>, - } - } - - def_struct! { - SetCompression 0x03 { - threshold: VarInt - } - } - - // ==== Serverbound ==== - - def_struct! { - LoginStart 0x00 { - username: BoundedString<3, 16>, - } - } - - def_struct! { - EncryptionResponse 0x01 { - shared_secret: BoundedArray, - verify_token: BoundedArray, + def_struct! { + EncryptionResponse 0x01 { + shared_secret: BoundedArray, + verify_token: BoundedArray, + } } } } /// Packets and types used during the play state. pub mod play { - use super::*; - - // ==== Clientbound ==== - - def_struct! { - SpawnEntity 0x00 { - entity_id: VarInt, - object_uuid: Uuid, - typ: VarInt, - position: Vec3, - pitch: ByteAngle, - yaw: ByteAngle, - data: i32, - velocity: Vec3, - } - } - - def_struct! { - SpawnExperienceOrb 0x01 { - entity_id: VarInt, - position: Vec3, - count: i16, - } - } - - def_struct! { - SpawnLivingEntity 0x02 { - entity_id: VarInt, - entity_uuid: Uuid, - typ: VarInt, - position: Vec3, - yaw: ByteAngle, - pitch: ByteAngle, - head_yaw: ByteAngle, - velocity: Vec3, - } - } - - def_struct! { - SpawnPainting 0x03 { - entity_id: VarInt, - entity_uuid: Uuid, - variant: VarInt, // TODO: painting ID enum - location: BlockPos, - direction: PaintingDirection, - } - } - - def_enum! { - PaintingDirection: u8 { - South = 0, - West = 1, - North = 2, - East = 3, - } - } - - def_struct! { - SpawnPlayer 0x04 { - entity_id: VarInt, - player_uuid: Uuid, - position: Vec3, - yaw: ByteAngle, - pitch: ByteAngle, - } - } - - def_struct! { - SculkVibrationSignal 0x05 { - source_position: BlockPos, - destination_identifier: Identifier, // TODO: destination codec type? - destination: BlockPos, // TODO: this type varies depending on destination_identifier - arrival_ticks: VarInt, - } - } - - def_struct! { - EntityAnimation 0x06 { - entity_id: VarInt, - animation: Animation, - } - } - - def_enum! { - Animation: u8 { - SwingMainArm = 0, - TakeDamage = 1, - LeaveBed = 2, - SwingOffhand = 3, - CriticalEffect = 4, - MagicCriticalEffect = 5, - } - } - - def_struct! { - AcknoledgePlayerDigging 0x08 { - location: BlockPos, - block: VarInt, // TODO: block state ID type. - status: VarInt, // TODO: VarInt enum here. - sucessful: bool, - } - } - - def_struct! { - BlockBreakAnimation 0x09 { - entity_id: VarInt, - location: BlockPos, - destroy_stage: BoundedInt, - } - } - - def_struct! { - BlockEntityData 0x0a { - location: BlockPos, - typ: VarInt, // TODO: use enum here - data: nbt::Blob, - } - } - - def_struct! { - BlockAction 0x0b { - location: BlockPos, - action_id: u8, - action_param: u8, - block_type: VarInt, - } - } - - def_struct! { - BlockChange 0x0c { - location: BlockPos, - block_id: VarInt, - } - } - - def_struct! { - BossBar 0x0d { - uuid: Uuid, - action: BossBarAction, - } - } - - def_enum! { - BossBarAction: VarInt { - Add: BossBarActionAdd = 0, - } - } - - def_struct! { - BossBarActionAdd { - title: Text, - health: f32, - color: BossBarColor, - division: BossBarDivision, - /// TODO: bitmask - flags: u8, - } - } - - def_enum! { - BossBarColor: VarInt { - Pink = 0, - Blue = 1, - Red = 2, - Green = 3, - Yellow = 4, - Purple = 5, - White = 6, - } - } - - def_enum! { - BossBarDivision: VarInt { - NoDivision = 0, - SixNotches = 1, - TenNotches = 2, - TwelveNotches = 3, - TwentyNotches = 4, - } - } - - def_struct! { - ServerDifficulty 0x0e { - difficulty: Difficulty, - locked: bool, - } - } - - def_enum! { - Difficulty: u8 { - Peaceful = 0, - Easy = 1, - Normal = 2, - Hard = 3, - } - } - - def_struct! { - ChatMessageClientbound 0x0f { - message: Text, - position: ChatMessagePosition, - sender: Uuid, - } - } - - def_enum! { - ChatMessagePosition: u8 { - Chat = 0, - SystemMessage = 1, - GameInfo = 2, - } - } - - def_struct! { - ClearTitles 0x10 { - reset: bool, - } - } - - def_struct! { - TabComplete 0x11 { - id: VarInt, - start: VarInt, - length: VarInt, - matches: Vec, - } - } - - def_struct! { - TabCompleteMatch { - value: String, - tooltip: TabCompleteTooltip, - } - } - - def_enum! { - TabCompleteTooltip: u8 { - NoTooltip = 0, - Tooltip: Text = 1, - } - } - - def_struct! { - WindowProperty 0x15 { - // TODO: use enums - window_id: u8, - property: i16, - value: i16, - } - } - - def_struct! { - SetCooldown 0x17 { - item_id: VarInt, - cooldown_ticks: VarInt, - } - } - - def_struct! { - Disconnect 0x1a { - reason: Text, - } - } - - def_struct! { - EntityStatus 0x1b { - entity_id: i32, - /// TODO: enum - entity_status: u8, - } - } - - def_struct! { - UnloadChunk 0x1d { - chunk_x: i32, - chunk_z: i32 - } - } - - def_struct! { - ChangeGameState 0x1e { - reason: ChangeGameStateReason, - value: f32, - } - } - - def_enum! { - ChangeGameStateReason: u8 { - NoRespawnBlockAvailable = 0, - EndRaining = 1, - BeginRaining = 2, - ChangeGameMode = 3, - WinGame = 4, - DemoEvent = 5, - ArrowHitPlayer = 6, - RainLevelChange = 7, - ThunderLevelChange = 8, - PlayPufferfishStingSound = 9, - PlayElderGuardianMobAppearance = 10, - EnableRespawnScreen = 11, - } - } - - def_struct! { - OpenHorseWindow 0x1f { - window_id: u8, - slot_count: VarInt, - entity_id: i32, - } - } - - def_struct! { - InitializeWorldBorder 0x20 { - x: f64, - z: f64, - old_diameter: f64, - new_diameter: f64, - speed: VarLong, - portal_teleport_boundary: VarInt, - warning_blocks: VarInt, - warning_time: VarInt, - } - } - - def_struct! { - KeepAliveClientbound 0x21 { - id: i64, - } - } - - def_struct! { - ChunkDataAndUpdateLight 0x22 { - chunk_x: i32, - chunk_z: i32, - heightmaps: Nbt, - blocks_and_biomes: Vec, - block_entities: Vec, - trust_edges: bool, - sky_light_mask: BitVec, - block_light_mask: BitVec, - empty_sky_light_mask: BitVec, - empty_block_light_mask: BitVec, - sky_light_arrays: Vec<[u8; 2048]>, - block_light_arrays: Vec<[u8; 2048]>, - } - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct ChunkDataHeightmaps { - #[serde(rename = "MOTION_BLOCKING", serialize_with = "nbt::i64_array")] - pub motion_blocking: Vec, - } - - def_struct! { - ChunkDataBlockEntity { - packed_xz: i8, - y: i16, - typ: VarInt, - data: nbt::Blob, - } - } - - def_struct! { - JoinGame 0x26 { - /// Entity ID of the joining player - entity_id: i32, - is_hardcore: bool, - gamemode: GameMode, - /// The previous gamemode for the purpose of the F3+F4 gamemode switcher. (TODO: verify) - /// Is `-1` if there was no previous gamemode. - previous_gamemode: GameMode, - dimension_names: Vec, - dimension_codec: Nbt, - /// The specification of the dimension being spawned into. - dimension: Nbt, - /// The identifier of the dimension being spawned into. - dimension_name: Identifier, - /// Hash of the world's seed used for client biome noise. - hashed_seed: i64, - /// No longer used by the client. - max_players: VarInt, - view_distance: BoundedInt, - simulation_distance: VarInt, - /// If reduced debug info should be shown on the F3 screen. - reduced_debug_info: bool, - /// If player respawns should be instant or not. - enable_respawn_screen: bool, - is_debug: bool, - /// If this is a superflat world. - /// Superflat worlds have different void fog and horizon levels. - is_flat: bool, - } - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct DimensionCodec { - #[serde(rename = "minecraft:dimension_type")] - pub dimension_type_registry: DimensionTypeRegistry, - #[serde(rename = "minecraft:worldgen/biome")] - pub biome_registry: BiomeRegistry, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct DimensionTypeRegistry { - #[serde(rename = "type")] - pub typ: Identifier, - pub value: Vec, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct DimensionTypeRegistryEntry { - pub name: Identifier, - pub id: i32, - pub element: DimensionType, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct DimensionType { - pub piglin_safe: bool, - pub natural: bool, - pub ambient_light: f32, - pub fixed_time: Option, - pub infiniburn: String, // TODO: tag type? - pub respawn_anchor_works: bool, - pub has_skylight: bool, - pub bed_works: bool, - pub effects: Identifier, - pub has_raids: bool, - pub min_y: i32, - pub height: i32, - pub logical_height: i32, - pub coordinate_scale: f64, - pub ultrawarm: bool, - pub has_ceiling: bool, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeRegistry { - #[serde(rename = "type")] - pub typ: Identifier, - pub value: Vec, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct Biome { - pub name: Identifier, - pub id: i32, - pub element: BiomeProperty, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeProperty { - pub precipitation: String, - pub depth: f32, - pub temperature: f32, - pub scale: f32, - pub downfall: f32, - pub category: String, - pub temperature_modifier: Option, - pub effects: BiomeEffects, - pub particle: Option, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeEffects { - pub sky_color: i32, - pub water_fog_color: i32, - pub fog_color: i32, - pub water_color: i32, - pub foliage_color: Option, - pub grass_color: Option, - pub grass_color_modifier: Option, - pub music: Option, - pub ambient_sound: Option, - pub additions_sound: Option, - pub mood_sound: Option, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeMusic { - pub replace_current_music: bool, - pub sound: Identifier, - pub max_delay: i32, - pub min_delay: i32, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeAdditionsSound { - pub sound: Identifier, - pub tick_chance: f64, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeMoodSound { - pub sound: Identifier, - pub tick_delay: i32, - pub offset: f64, - pub block_search_extent: i32, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeParticle { - pub probability: f32, - pub options: BiomeParticleOptions, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - pub struct BiomeParticleOptions { - #[serde(rename = "type")] - pub typ: Identifier, - } - - def_enum! { - #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - GameMode: u8 { - Survival = 0, - Creative = 1, - Adventure = 2, - Spectator = 3, - } - } - - impl Default for GameMode { - fn default() -> Self { - GameMode::Survival - } - } - - def_struct! { - EntityPosition 0x29 { - entity_id: VarInt, - delta: Vec3, - on_ground: bool, - } - } - - def_struct! { - EntityPositionAndRotation 0x2a { - entity_id: VarInt, - delta: Vec3, - yaw: ByteAngle, - pitch: ByteAngle, - on_ground: bool, - } - } - - def_struct! { - EntityRotation 0x2b { - entity_id: VarInt, - yaw: ByteAngle, - pitch: ByteAngle, - on_ground: bool, - } - } - - def_struct! { - PlayerPositionAndLook 0x38 { - position: Vec3, - yaw: f32, - pitch: f32, - flags: PlayerPositionAndLookFlags, - teleport_id: VarInt, - dismount_vehicle: bool, - } - } - - def_bitfield! { - PlayerPositionAndLookFlags: u8 { - x = 0, - y = 1, - z = 2, - y_rot = 3, - x_rot = 4, - } - } - - def_struct! { - DestroyEntities 0x3a { - entities: Vec, - } - } - - def_struct! { - EntityHeadLook 0x3e { - entity_id: VarInt, - head_yaw: ByteAngle, - } - } - - def_struct! { - MultiBlockChange 0x3f { - chunk_section_position: u64, - invert_trust_edges: bool, - blocks: Vec, - } - } - - def_struct! { - HeldItemChangeClientbound 0x48 { - slot: BoundedInt, - } - } - - def_struct! { - UpdateViewPosition 0x49 { - chunk_x: VarInt, - chunk_z: VarInt, - } - } - - def_struct! { - UpdateViewDistance 0x4a { - view_distance: BoundedInt, - } - } - - def_struct! { - SpawnPosition 0x4b { - location: BlockPos, - angle: f32, - } - } - - def_struct! { - EntityMetadata 0x4d { - entity_id: VarInt, - metadata: RawBytes, - } - } - - def_struct! { - EntityVelocity 0x4f { - entity_id: VarInt, - velocity: Vec3, - } - } - - def_struct! { - TimeUpdate 0x59 { - /// The age of the world in 1/20ths of a second. - world_age: i64, - /// The current time of day in 1/20ths of a second. - /// The value should be in the range \[0, 24000]. - /// 6000 is noon, 12000 is sunset, and 18000 is midnight. - time_of_day: i64, - } - } - - def_struct! { - EntityTeleport 0x62 { - entity_id: VarInt, - position: Vec3, - yaw: ByteAngle, - pitch: ByteAngle, - on_ground: bool, - } - } - - macro_rules! def_client_play_packet_enum { - { - $($packet:ident),* $(,)? - } => { - /// An enum of all clientbound play packets. - #[derive(Clone, Debug)] - pub enum ClientPlayPacket { - $($packet($packet)),* + pub mod s2c { + use super::super::*; + + def_struct! { + SpawnEntity 0x00 { + entity_id: VarInt, + object_uuid: Uuid, + typ: VarInt, + position: Vec3, + pitch: ByteAngle, + yaw: ByteAngle, + data: i32, + velocity: Vec3, } + } - impl private::Sealed for ClientPlayPacket {} + def_struct! { + SpawnExperienceOrb 0x01 { + entity_id: VarInt, + position: Vec3, + count: i16, + } + } - $( - impl From<$packet> for ClientPlayPacket { - fn from(p: $packet) -> ClientPlayPacket { - ClientPlayPacket::$packet(p) + def_struct! { + SpawnLivingEntity 0x02 { + entity_id: VarInt, + entity_uuid: Uuid, + typ: VarInt, + position: Vec3, + yaw: ByteAngle, + pitch: ByteAngle, + head_yaw: ByteAngle, + velocity: Vec3, + } + } + + def_struct! { + SpawnPainting 0x03 { + entity_id: VarInt, + entity_uuid: Uuid, + variant: VarInt, // TODO: painting ID enum + location: BlockPos, + direction: PaintingDirection, + } + } + + def_enum! { + PaintingDirection: u8 { + South = 0, + West = 1, + North = 2, + East = 3, + } + } + + def_struct! { + SpawnPlayer 0x04 { + entity_id: VarInt, + player_uuid: Uuid, + position: Vec3, + yaw: ByteAngle, + pitch: ByteAngle, + } + } + + def_struct! { + SculkVibrationSignal 0x05 { + source_position: BlockPos, + destination_identifier: Ident, // TODO: destination codec type? + destination: BlockPos, // TODO: this type varies depending on destination_identifier + arrival_ticks: VarInt, + } + } + + def_struct! { + EntityAnimation 0x06 { + entity_id: VarInt, + animation: Animation, + } + } + + def_enum! { + Animation: u8 { + SwingMainArm = 0, + TakeDamage = 1, + LeaveBed = 2, + SwingOffhand = 3, + CriticalEffect = 4, + MagicCriticalEffect = 5, + } + } + + def_struct! { + AcknoledgePlayerDigging 0x08 { + location: BlockPos, + block: VarInt, // TODO: block state ID type. + status: VarInt, // TODO: VarInt enum here. + sucessful: bool, + } + } + + def_struct! { + BlockBreakAnimation 0x09 { + entity_id: VarInt, + location: BlockPos, + destroy_stage: BoundedInt, + } + } + + def_struct! { + BlockEntityData 0x0a { + location: BlockPos, + typ: VarInt, // TODO: use enum here + data: nbt::Blob, + } + } + + def_struct! { + BlockAction 0x0b { + location: BlockPos, + action_id: u8, + action_param: u8, + block_type: VarInt, + } + } + + def_struct! { + BlockChange 0x0c { + location: BlockPos, + block_id: VarInt, + } + } + + def_struct! { + BossBar 0x0d { + uuid: Uuid, + action: BossBarAction, + } + } + + def_enum! { + BossBarAction: VarInt { + Add: BossBarActionAdd = 0, + } + } + + def_struct! { + BossBarActionAdd { + title: Text, + health: f32, + color: BossBarColor, + division: BossBarDivision, + /// TODO: bitmask + flags: u8, + } + } + + def_enum! { + BossBarColor: VarInt { + Pink = 0, + Blue = 1, + Red = 2, + Green = 3, + Yellow = 4, + Purple = 5, + White = 6, + } + } + + def_enum! { + BossBarDivision: VarInt { + NoDivision = 0, + SixNotches = 1, + TenNotches = 2, + TwelveNotches = 3, + TwentyNotches = 4, + } + } + + def_struct! { + ServerDifficulty 0x0e { + difficulty: Difficulty, + locked: bool, + } + } + + def_enum! { + Difficulty: u8 { + Peaceful = 0, + Easy = 1, + Normal = 2, + Hard = 3, + } + } + + def_struct! { + ChatMessageClientbound 0x0f { + message: Text, + position: ChatMessagePosition, + sender: Uuid, + } + } + + def_enum! { + ChatMessagePosition: u8 { + Chat = 0, + SystemMessage = 1, + GameInfo = 2, + } + } + + def_struct! { + ClearTitles 0x10 { + reset: bool, + } + } + + def_struct! { + TabComplete 0x11 { + id: VarInt, + start: VarInt, + length: VarInt, + matches: Vec, + } + } + + def_struct! { + TabCompleteMatch { + value: String, + tooltip: TabCompleteTooltip, + } + } + + def_enum! { + TabCompleteTooltip: u8 { + NoTooltip = 0, + Tooltip: Text = 1, + } + } + + def_struct! { + WindowProperty 0x15 { + // TODO: use enums + window_id: u8, + property: i16, + value: i16, + } + } + + def_struct! { + SetCooldown 0x17 { + item_id: VarInt, + cooldown_ticks: VarInt, + } + } + + def_struct! { + Disconnect 0x1a { + reason: Text, + } + } + + def_struct! { + EntityStatus 0x1b { + entity_id: i32, + /// TODO: enum + entity_status: u8, + } + } + + def_struct! { + UnloadChunk 0x1d { + chunk_x: i32, + chunk_z: i32 + } + } + + def_struct! { + ChangeGameState 0x1e { + reason: ChangeGameStateReason, + value: f32, + } + } + + def_enum! { + ChangeGameStateReason: u8 { + NoRespawnBlockAvailable = 0, + EndRaining = 1, + BeginRaining = 2, + ChangeGameMode = 3, + WinGame = 4, + DemoEvent = 5, + ArrowHitPlayer = 6, + RainLevelChange = 7, + ThunderLevelChange = 8, + PlayPufferfishStingSound = 9, + PlayElderGuardianMobAppearance = 10, + EnableRespawnScreen = 11, + } + } + + def_struct! { + OpenHorseWindow 0x1f { + window_id: u8, + slot_count: VarInt, + entity_id: i32, + } + } + + def_struct! { + InitializeWorldBorder 0x20 { + x: f64, + z: f64, + old_diameter: f64, + new_diameter: f64, + speed: VarLong, + portal_teleport_boundary: VarInt, + warning_blocks: VarInt, + warning_time: VarInt, + } + } + + def_struct! { + KeepAliveClientbound 0x21 { + id: i64, + } + } + + def_struct! { + ChunkDataAndUpdateLight 0x22 { + chunk_x: i32, + chunk_z: i32, + heightmaps: Nbt, + blocks_and_biomes: Vec, + block_entities: Vec, + trust_edges: bool, + sky_light_mask: BitVec, + block_light_mask: BitVec, + empty_sky_light_mask: BitVec, + empty_block_light_mask: BitVec, + sky_light_arrays: Vec<[u8; 2048]>, + block_light_arrays: Vec<[u8; 2048]>, + } + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct ChunkDataHeightmaps { + #[serde(rename = "MOTION_BLOCKING", serialize_with = "nbt::i64_array")] + pub motion_blocking: Vec, + } + + def_struct! { + ChunkDataBlockEntity { + packed_xz: i8, + y: i16, + typ: VarInt, + data: nbt::Blob, + } + } + + def_struct! { + JoinGame 0x26 { + /// Entity ID of the joining player + entity_id: i32, + is_hardcore: bool, + gamemode: GameMode, + /// The previous gamemode for the purpose of the F3+F4 gamemode switcher. (TODO: verify) + /// Is `-1` if there was no previous gamemode. + previous_gamemode: GameMode, + dimension_names: Vec, + dimension_codec: Nbt, + /// The specification of the dimension being spawned into. + dimension: Nbt, + /// The identifier of the dimension being spawned into. + dimension_name: Ident, + /// Hash of the world's seed used for client biome noise. + hashed_seed: i64, + /// No longer used by the client. + max_players: VarInt, + view_distance: BoundedInt, + simulation_distance: VarInt, + /// If reduced debug info should be shown on the F3 screen. + reduced_debug_info: bool, + /// If player respawns should be instant or not. + enable_respawn_screen: bool, + is_debug: bool, + /// If this is a superflat world. + /// Superflat worlds have different void fog and horizon levels. + is_flat: bool, + } + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct DimensionCodec { + #[serde(rename = "minecraft:dimension_type")] + pub dimension_type_registry: DimensionTypeRegistry, + #[serde(rename = "minecraft:worldgen/biome")] + pub biome_registry: BiomeRegistry, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct DimensionTypeRegistry { + #[serde(rename = "type")] + pub typ: Ident, + pub value: Vec, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct DimensionTypeRegistryEntry { + pub name: Ident, + pub id: i32, + pub element: DimensionType, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct DimensionType { + pub piglin_safe: bool, + pub natural: bool, + pub ambient_light: f32, + pub fixed_time: Option, + pub infiniburn: String, // TODO: tag type? + pub respawn_anchor_works: bool, + pub has_skylight: bool, + pub bed_works: bool, + pub effects: Ident, + pub has_raids: bool, + pub min_y: i32, + pub height: i32, + pub logical_height: i32, + pub coordinate_scale: f64, + pub ultrawarm: bool, + pub has_ceiling: bool, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeRegistry { + #[serde(rename = "type")] + pub typ: Ident, + pub value: Vec, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct Biome { + pub name: Ident, + pub id: i32, + pub element: BiomeProperty, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeProperty { + pub precipitation: String, + pub depth: f32, + pub temperature: f32, + pub scale: f32, + pub downfall: f32, + pub category: String, + pub temperature_modifier: Option, + pub effects: BiomeEffects, + pub particle: Option, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeEffects { + pub sky_color: i32, + pub water_fog_color: i32, + pub fog_color: i32, + pub water_color: i32, + pub foliage_color: Option, + pub grass_color: Option, + pub grass_color_modifier: Option, + pub music: Option, + pub ambient_sound: Option, + pub additions_sound: Option, + pub mood_sound: Option, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeMusic { + pub replace_current_music: bool, + pub sound: Ident, + pub max_delay: i32, + pub min_delay: i32, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeAdditionsSound { + pub sound: Ident, + pub tick_chance: f64, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeMoodSound { + pub sound: Ident, + pub tick_delay: i32, + pub offset: f64, + pub block_search_extent: i32, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeParticle { + pub probability: f32, + pub options: BiomeParticleOptions, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct BiomeParticleOptions { + #[serde(rename = "type")] + pub typ: Ident, + } + + def_enum! { + #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + GameMode: u8 { + Survival = 0, + Creative = 1, + Adventure = 2, + Spectator = 3, + } + } + + impl Default for GameMode { + fn default() -> Self { + GameMode::Survival + } + } + + def_struct! { + EntityPosition 0x29 { + entity_id: VarInt, + delta: Vec3, + on_ground: bool, + } + } + + def_struct! { + EntityPositionAndRotation 0x2a { + entity_id: VarInt, + delta: Vec3, + yaw: ByteAngle, + pitch: ByteAngle, + on_ground: bool, + } + } + + def_struct! { + EntityRotation 0x2b { + entity_id: VarInt, + yaw: ByteAngle, + pitch: ByteAngle, + on_ground: bool, + } + } + + def_struct! { + PlayerPositionAndLook 0x38 { + position: Vec3, + yaw: f32, + pitch: f32, + flags: PlayerPositionAndLookFlags, + teleport_id: VarInt, + dismount_vehicle: bool, + } + } + + def_bitfield! { + PlayerPositionAndLookFlags: u8 { + x = 0, + y = 1, + z = 2, + y_rot = 3, + x_rot = 4, + } + } + + def_struct! { + DestroyEntities 0x3a { + entities: Vec, + } + } + + def_struct! { + EntityHeadLook 0x3e { + entity_id: VarInt, + head_yaw: ByteAngle, + } + } + + def_struct! { + MultiBlockChange 0x3f { + chunk_section_position: u64, + invert_trust_edges: bool, + blocks: Vec, + } + } + + def_struct! { + HeldItemChangeClientbound 0x48 { + slot: BoundedInt, + } + } + + def_struct! { + UpdateViewPosition 0x49 { + chunk_x: VarInt, + chunk_z: VarInt, + } + } + + def_struct! { + UpdateViewDistance 0x4a { + view_distance: BoundedInt, + } + } + + def_struct! { + SpawnPosition 0x4b { + location: BlockPos, + angle: f32, + } + } + + def_struct! { + EntityMetadata 0x4d { + entity_id: VarInt, + metadata: RawBytes, + } + } + + def_struct! { + EntityVelocity 0x4f { + entity_id: VarInt, + velocity: Vec3, + } + } + + def_struct! { + TimeUpdate 0x59 { + /// The age of the world in 1/20ths of a second. + world_age: i64, + /// The current time of day in 1/20ths of a second. + /// The value should be in the range \[0, 24000]. + /// 6000 is noon, 12000 is sunset, and 18000 is midnight. + time_of_day: i64, + } + } + + def_struct! { + EntityTeleport 0x62 { + entity_id: VarInt, + position: Vec3, + yaw: ByteAngle, + pitch: ByteAngle, + on_ground: bool, + } + } + + macro_rules! def_s2c_play_packet_enum { + { + $($packet:ident),* $(,)? + } => { + /// An enum of all s2c play packets. + #[derive(Clone, Debug)] + pub enum S2cPlayPacket { + $($packet($packet)),* + } + + impl private::Sealed for S2cPlayPacket {} + + $( + impl From<$packet> for S2cPlayPacket { + fn from(p: $packet) -> S2cPlayPacket { + S2cPlayPacket::$packet(p) + } + } + )* + + impl EncodePacket for S2cPlayPacket { + fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> { + match self { + $( + Self::$packet(p) => { + VarInt($packet::PACKET_ID) + .encode(w) + .context(concat!("failed to write s2c play packet ID for `", stringify!($packet), "`"))?; + p.encode(w) + } + )* + } } } - )* - impl EncodePacket for ClientPlayPacket { - fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> { - match self { + #[cfg(test)] + #[test] + fn test_s2c_play_packet_order() { + let ids = [ $( - Self::$packet(p) => { - VarInt($packet::PACKET_ID) - .encode(w) - .context(concat!("failed to write play packet ID for `", stringify!($packet), "`"))?; - p.encode(w) - } + (stringify!($packet), $packet::PACKET_ID), )* + ]; + + if let Some(w) = ids.windows(2).find(|w| w[0].1 >= w[1].1) { + panic!("the {} and {} variants of the s2c play packet enum are not properly sorted by their packet ID", w[0].0, w[1].0); } } } + } - #[cfg(test)] - #[test] - fn test_client_play_packet_order() { - let ids = [ - $( - (stringify!($packet), $packet::PACKET_ID), - )* - ]; + def_s2c_play_packet_enum! { + SpawnEntity, + SpawnExperienceOrb, + SpawnLivingEntity, + SpawnPainting, + SpawnPlayer, + SculkVibrationSignal, + EntityAnimation, + AcknoledgePlayerDigging, + BlockBreakAnimation, + BlockEntityData, + BlockAction, + BlockChange, + BossBar, + Disconnect, + EntityStatus, + UnloadChunk, + ChangeGameState, + KeepAliveClientbound, + ChunkDataAndUpdateLight, + JoinGame, + EntityPosition, + EntityPositionAndRotation, + EntityRotation, + PlayerPositionAndLook, + DestroyEntities, + EntityHeadLook, + MultiBlockChange, + HeldItemChangeClientbound, + UpdateViewPosition, + UpdateViewDistance, + SpawnPosition, + EntityMetadata, + EntityVelocity, + EntityTeleport, + TimeUpdate, + } + } - if let Some(w) = ids.windows(2).find(|w| w[0].1 >= w[1].1) { - panic!("the {} and {} variants of the client play packet enum are not properly sorted by their packet ID", w[0].0, w[1].0); - } + pub mod c2s { + use super::super::*; + + def_struct! { + TeleportConfirm 0x00 { + teleport_id: VarInt } } - } - - def_client_play_packet_enum! { - SpawnEntity, - SpawnExperienceOrb, - SpawnLivingEntity, - SpawnPainting, - SpawnPlayer, - SculkVibrationSignal, - EntityAnimation, - AcknoledgePlayerDigging, - BlockBreakAnimation, - BlockEntityData, - BlockAction, - BlockChange, - BossBar, - Disconnect, - EntityStatus, - UnloadChunk, - ChangeGameState, - KeepAliveClientbound, - ChunkDataAndUpdateLight, - JoinGame, - EntityPosition, - EntityPositionAndRotation, - EntityRotation, - PlayerPositionAndLook, - DestroyEntities, - EntityHeadLook, - MultiBlockChange, - HeldItemChangeClientbound, - UpdateViewPosition, - UpdateViewDistance, - SpawnPosition, - EntityMetadata, - EntityVelocity, - EntityTeleport, - TimeUpdate, - } - - // ==== Serverbound ==== - - def_struct! { - TeleportConfirm 0x00 { - teleport_id: VarInt - } - } - - def_struct! { - QueryBlockNbt 0x01 { - transaction_id: VarInt, - location: BlockPos, - } - } - - def_enum! { - SetDifficulty 0x02: i8 { - Peaceful = 0, - Easy = 1, - Normal = 2, - Hard = 3, - } - } - - def_struct! { - ChatMessageServerbound 0x03 { - message: BoundedString<0, 256> - } - } - - def_enum! { - ClientStatus 0x04: VarInt { - /// Sent when ready to complete login and ready to respawn after death. - PerformRespawn = 0, - /// Sent when the statistics menu is opened. - RequestStatus = 1, - } - } - - def_struct! { - ClientSettings 0x05 { - /// e.g. en_US - locale: BoundedString<0, 16>, - /// Client-side render distance in chunks. - view_distance: BoundedInt, - chat_mode: ChatMode, - chat_colors: bool, - displayed_skin_parts: DisplayedSkinParts, - main_hand: MainHand, - /// Currently always false - enable_text_filtering: bool, - /// False if the client should not show up in the hover preview. - allow_server_listings: bool, - } - } - - def_enum! { - #[derive(Copy, PartialEq, Eq)] - ChatMode: VarInt { - Enabled = 0, - CommandsOnly = 1, - Hidden = 2, - } - } - - def_bitfield! { - DisplayedSkinParts: u8 { - cape = 0, - jacket = 1, - left_sleeve = 2, - right_sleeve = 3, - left_pants_leg = 4, - right_pants_leg = 5, - hat = 6, - } - } - - def_enum! { - #[derive(Copy, PartialEq, Eq)] - MainHand: VarInt { - Left = 0, - Right = 1, - } - } - - def_struct! { - TabCompleteServerbound 0x06 { - transaction_id: VarInt, - /// Text behind the cursor without the '/'. - text: BoundedString<0, 32500> - } - } - - def_struct! { - ClickWindowButton 0x07 { - window_id: i8, - button_id: i8, - } - } - def_struct! { - ClickWindow 0x08 { - window_id: u8, - state_id: VarInt, - slot: i16, - button: i8, - mode: VarInt, // TODO: enum - // TODO - } - } - - def_struct! { - CloseWindow 0x09 { - window_id: u8, - } - } - - def_struct! { - PluginMessageServerbound 0x0a { - channel: Identifier, - data: RawBytes, - } - } - - def_struct! { - EditBook 0x0b { - hand: Hand, - entries: Vec, - title: Option, - } - } - - def_enum! { - Hand: VarInt { - Main = 0, - Off = 1, - } - } - - def_struct! { - QueryEntityNbt 0x0c { - transaction_id: VarInt, - entity_id: VarInt, - } - } - - def_struct! { - InteractEntity 0x0d { - entity_id: VarInt, - typ: InteractType, - sneaking: bool, + def_struct! { + QueryBlockNbt 0x01 { + transaction_id: VarInt, + location: BlockPos, + } + } + + def_enum! { + SetDifficulty 0x02: i8 { + Peaceful = 0, + Easy = 1, + Normal = 2, + Hard = 3, + } + } + + def_struct! { + ChatMessageServerbound 0x03 { + message: BoundedString<0, 256> + } + } + + def_enum! { + ClientStatus 0x04: VarInt { + /// Sent when ready to complete login and ready to respawn after death. + PerformRespawn = 0, + /// Sent when the statistics menu is opened. + RequestStatus = 1, + } + } + + def_struct! { + ClientSettings 0x05 { + /// e.g. en_US + locale: BoundedString<0, 16>, + /// Client-side render distance in chunks. + view_distance: BoundedInt, + chat_mode: ChatMode, + chat_colors: bool, + displayed_skin_parts: DisplayedSkinParts, + main_hand: MainHand, + /// Currently always false + enable_text_filtering: bool, + /// False if the client should not show up in the hover preview. + allow_server_listings: bool, + } + } + + def_enum! { + #[derive(Copy, PartialEq, Eq)] + ChatMode: VarInt { + Enabled = 0, + CommandsOnly = 1, + Hidden = 2, + } + } + + def_bitfield! { + DisplayedSkinParts: u8 { + cape = 0, + jacket = 1, + left_sleeve = 2, + right_sleeve = 3, + left_pants_leg = 4, + right_pants_leg = 5, + hat = 6, + } + } + + def_enum! { + #[derive(Copy, PartialEq, Eq)] + MainHand: VarInt { + Left = 0, + Right = 1, + } + } + + def_struct! { + TabCompleteServerbound 0x06 { + transaction_id: VarInt, + /// Text behind the cursor without the '/'. + text: BoundedString<0, 32500> + } + } + + def_struct! { + ClickWindowButton 0x07 { + window_id: i8, + button_id: i8, + } + } + + def_struct! { + ClickWindow 0x08 { + window_id: u8, + state_id: VarInt, + slot: i16, + button: i8, + mode: VarInt, // TODO: enum + // TODO + } + } + + def_struct! { + CloseWindow 0x09 { + window_id: u8, + } + } + + def_struct! { + PluginMessageServerbound 0x0a { + channel: Ident, + data: RawBytes, + } + } + + def_struct! { + EditBook 0x0b { + hand: Hand, + entries: Vec, + title: Option, + } + } + + def_enum! { + Hand: VarInt { + Main = 0, + Off = 1, + } + } + + def_struct! { + QueryEntityNbt 0x0c { + transaction_id: VarInt, + entity_id: VarInt, + } + } + + def_struct! { + InteractEntity 0x0d { + entity_id: VarInt, + typ: InteractType, + sneaking: bool, + } } - } - def_enum! { - InteractType: VarInt { - Interact: Hand = 0, - Attack = 1, - InteractAt: InteractAtData = 2 + def_enum! { + InteractType: VarInt { + Interact: Hand = 0, + Attack = 1, + InteractAt: InteractAtData = 2 + } } - } - def_struct! { - InteractAtData { - target: Vec3, - hand: Hand, + def_struct! { + InteractAtData { + target: Vec3, + hand: Hand, + } } - } - def_struct! { - GenerateStructure 0x0e { - location: BlockPos, - levels: VarInt, - keep_jigsaws: bool, + def_struct! { + GenerateStructure 0x0e { + location: BlockPos, + levels: VarInt, + keep_jigsaws: bool, + } } - } - def_struct! { - KeepAliveServerbound 0x0f { - id: i64, + def_struct! { + KeepAliveServerbound 0x0f { + id: i64, + } } - } - def_struct! { - LockDifficulty 0x10 { - locked: bool + def_struct! { + LockDifficulty 0x10 { + locked: bool + } } - } - def_struct! { - PlayerPosition 0x11 { - position: Vec3, - on_ground: bool, - } - } - - def_struct! { - PlayerPositionAndRotation 0x12 { - // Absolute position - position: Vec3, - /// Absolute rotation on X axis in degrees. - yaw: f32, - /// Absolute rotation on Y axis in degrees. - pitch: f32, - on_ground: bool, - } - } + def_struct! { + PlayerPosition 0x11 { + position: Vec3, + on_ground: bool, + } + } + + def_struct! { + PlayerPositionAndRotation 0x12 { + // Absolute position + position: Vec3, + /// Absolute rotation on X axis in degrees. + yaw: f32, + /// Absolute rotation on Y axis in degrees. + pitch: f32, + on_ground: bool, + } + } - def_struct! { - PlayerRotation 0x13 { - /// Absolute rotation on X axis in degrees. - yaw: f32, - /// Absolute rotation on Y axis in degrees. - pitch: f32, - on_ground: bool, - } - } - - def_struct! { - PlayerMovement 0x14 { - on_ground: bool - } - } - - def_struct! { - VehicleMoveServerbound 0x15 { - /// Absolute position - position: Vec3, - /// Degrees - yaw: f32, - /// Degrees - pitch: f32, - } - } - - def_struct! { - SteerBoat 0x16 { - left_paddle_turning: bool, - right_paddle_turning: bool, - } - } - - def_struct! { - PickItem 0x17 { - slot_to_use: VarInt, - } - } - - def_struct! { - CraftRecipeRequest 0x18 { - window_id: i8, - recipe: Identifier, - make_all: bool, - } - } - - def_enum! { - PlayerAbilitiesServerbound 0x19: i8 { - NotFlying = 0, - Flying = 0b10, - } - } - - def_struct! { - PlayerDigging 0x1a { - status: DiggingStatus, - location: BlockPos, - face: BlockFace, - } - } - - def_enum! { - DiggingStatus: VarInt { - StartedDigging = 0, - CancelledDigging = 1, - FinishedDigging = 2, - DropItemStack = 3, - DropItem = 4, - ShootArrowOrFinishEating = 5, - SwapItemInHand = 6, - } - } - - def_enum! { - BlockFace: i8 { - /// -Y - Bottom = 0, - /// +Y - Top = 1, - /// -Z - North = 2, - /// +Z - South = 3, - /// -X - West = 4, - /// +X - East = 5, - } - } - - def_struct! { - EntityAction 0x1b { - entity_id: VarInt, - action_id: EntityActionId, - jump_boost: BoundedInt, - } - } - - def_enum! { - EntityActionId: VarInt { - StartSneaking = 0, - StopSneaking = 1, - LeaveBed = 2, - StartSprinting = 3, - StopSprinting = 4, - StartJumpWithHorse = 5, - StopJumpWithHorse = 6, - OpenHorseInventory = 7, - StartFlyingWithElytra = 8, - } - } - - def_struct! { - SteerVehicle 0x1c { - sideways: f32, - forward: f32, - flags: SteerVehicleFlags, - } - } - - def_bitfield! { - SteerVehicleFlags: u8 { - jump = 0, - unmount = 1, - } - } - - def_struct! { - Pong 0x1d { - id: i32, - } - } - - def_struct! { - SetRecipeBookState 0x1e { - book_id: RecipeBookId, - book_open: bool, - filter_active: bool, - } - } - - def_enum! { - RecipeBookId: VarInt { - Crafting = 0, - Furnace = 1, - BlastFurnace = 2, - Smoker = 3, - } - } - - def_struct! { - SetDisplayedRecipe 0x1f { - recipe_id: Identifier, - } - } - - def_struct! { - NameItem 0x20 { - item_name: BoundedString<0, 50>, - } - } - - def_enum! { - ResourcePackStatus 0x21: VarInt { - SuccessfullyLoaded = 0, - Declined = 1, - FailedDownload = 2, - Accepted = 3, - } - } - - def_enum! { - AdvancementTab 0x22: VarInt { - OpenedTab: Identifier = 0, - ClosedScreen = 1, - } - } - - def_struct! { - SelectTrade 0x23 { - selected_slot: VarInt, - } - } - - def_struct! { - SetBeaconEffect 0x24 { - // TODO: potion ids? - primary_effect: VarInt, - secondary_effect: VarInt, - } - } - - def_struct! { - HeldItemChangeServerbound 0x25 { - slot: BoundedInt, - } - } - - def_struct! { - UpdateCommandBlock 0x26 { - location: BlockPos, - command: String, - mode: CommandBlockMode, - flags: CommandBlockFlags, - } - } - - def_enum! { - CommandBlockMode: VarInt { - Sequence = 0, - Auto = 1, - Redstone = 2, - } - } - - def_bitfield! { - CommandBlockFlags: i8 { - track_output = 0, - is_conditional = 1, - automatic = 2, - } - } + def_struct! { + PlayerRotation 0x13 { + /// Absolute rotation on X axis in degrees. + yaw: f32, + /// Absolute rotation on Y axis in degrees. + pitch: f32, + on_ground: bool, + } + } + + def_struct! { + PlayerMovement 0x14 { + on_ground: bool + } + } + + def_struct! { + VehicleMoveServerbound 0x15 { + /// Absolute position + position: Vec3, + /// Degrees + yaw: f32, + /// Degrees + pitch: f32, + } + } + + def_struct! { + SteerBoat 0x16 { + left_paddle_turning: bool, + right_paddle_turning: bool, + } + } + + def_struct! { + PickItem 0x17 { + slot_to_use: VarInt, + } + } + + def_struct! { + CraftRecipeRequest 0x18 { + window_id: i8, + recipe: Ident, + make_all: bool, + } + } + + def_enum! { + PlayerAbilitiesServerbound 0x19: i8 { + NotFlying = 0, + Flying = 0b10, + } + } + + def_struct! { + PlayerDigging 0x1a { + status: DiggingStatus, + location: BlockPos, + face: BlockFace, + } + } + + def_enum! { + DiggingStatus: VarInt { + StartedDigging = 0, + CancelledDigging = 1, + FinishedDigging = 2, + DropItemStack = 3, + DropItem = 4, + ShootArrowOrFinishEating = 5, + SwapItemInHand = 6, + } + } + + def_enum! { + BlockFace: i8 { + /// -Y + Bottom = 0, + /// +Y + Top = 1, + /// -Z + North = 2, + /// +Z + South = 3, + /// -X + West = 4, + /// +X + East = 5, + } + } + + def_struct! { + EntityAction 0x1b { + entity_id: VarInt, + action_id: EntityActionId, + jump_boost: BoundedInt, + } + } + + def_enum! { + EntityActionId: VarInt { + StartSneaking = 0, + StopSneaking = 1, + LeaveBed = 2, + StartSprinting = 3, + StopSprinting = 4, + StartJumpWithHorse = 5, + StopJumpWithHorse = 6, + OpenHorseInventory = 7, + StartFlyingWithElytra = 8, + } + } + + def_struct! { + SteerVehicle 0x1c { + sideways: f32, + forward: f32, + flags: SteerVehicleFlags, + } + } + + def_bitfield! { + SteerVehicleFlags: u8 { + jump = 0, + unmount = 1, + } + } + + def_struct! { + Pong 0x1d { + id: i32, + } + } + + def_struct! { + SetRecipeBookState 0x1e { + book_id: RecipeBookId, + book_open: bool, + filter_active: bool, + } + } + + def_enum! { + RecipeBookId: VarInt { + Crafting = 0, + Furnace = 1, + BlastFurnace = 2, + Smoker = 3, + } + } + + def_struct! { + SetDisplayedRecipe 0x1f { + recipe_id: Ident, + } + } + + def_struct! { + NameItem 0x20 { + item_name: BoundedString<0, 50>, + } + } + + def_enum! { + ResourcePackStatus 0x21: VarInt { + SuccessfullyLoaded = 0, + Declined = 1, + FailedDownload = 2, + Accepted = 3, + } + } + + def_enum! { + AdvancementTab 0x22: VarInt { + OpenedTab: Ident = 0, + ClosedScreen = 1, + } + } + + def_struct! { + SelectTrade 0x23 { + selected_slot: VarInt, + } + } + + def_struct! { + SetBeaconEffect 0x24 { + // TODO: potion ids? + primary_effect: VarInt, + secondary_effect: VarInt, + } + } + + def_struct! { + HeldItemChangeServerbound 0x25 { + slot: BoundedInt, + } + } + + def_struct! { + UpdateCommandBlock 0x26 { + location: BlockPos, + command: String, + mode: CommandBlockMode, + flags: CommandBlockFlags, + } + } + + def_enum! { + CommandBlockMode: VarInt { + Sequence = 0, + Auto = 1, + Redstone = 2, + } + } + + def_bitfield! { + CommandBlockFlags: i8 { + track_output = 0, + is_conditional = 1, + automatic = 2, + } + } - def_struct! { - UpdateCommandBlockMinecart 0x27 { - entity_id: VarInt, - command: String, - track_output: bool, + def_struct! { + UpdateCommandBlockMinecart 0x27 { + entity_id: VarInt, + command: String, + track_output: bool, + } } - } - def_struct! { - CreativeInventoryAction 0x28 { - slot: i16, - // TODO: clicked_item: Slot, - } - } + def_struct! { + CreativeInventoryAction 0x28 { + slot: i16, + // TODO: clicked_item: Slot, + } + } - def_struct! { - UpdateJigsawBlock 0x29 { - location: BlockPos, - name: Identifier, - target: Identifier, - pool: Identifier, - final_state: String, - joint_type: String, + def_struct! { + UpdateJigsawBlock 0x29 { + location: BlockPos, + name: Ident, + target: Ident, + pool: Ident, + final_state: String, + joint_type: String, + } } - } - def_struct! { - UpdateStructureBlock 0x2a { - location: BlockPos, - action: StructureBlockAction, - mode: StructureBlockMode, - name: String, - offset_xyz: [BoundedInt; 3], - size_xyz: [BoundedInt; 3], - mirror: StructureBlockMirror, - rotation: StructureBlockRotation, - metadata: String, - integrity: f32, // TODO: bounded float between 0 and 1. - seed: VarLong, - flags: StructureBlockFlags, - } - } - - def_enum! { - StructureBlockAction: VarInt { - UpdateData = 0, - SaveStructure = 1, - LoadStructure = 2, - DetectSize = 3, - } - } + def_struct! { + UpdateStructureBlock 0x2a { + location: BlockPos, + action: StructureBlockAction, + mode: StructureBlockMode, + name: String, + offset_xyz: [BoundedInt; 3], + size_xyz: [BoundedInt; 3], + mirror: StructureBlockMirror, + rotation: StructureBlockRotation, + metadata: String, + integrity: f32, // TODO: bounded float between 0 and 1. + seed: VarLong, + flags: StructureBlockFlags, + } + } + + def_enum! { + StructureBlockAction: VarInt { + UpdateData = 0, + SaveStructure = 1, + LoadStructure = 2, + DetectSize = 3, + } + } - def_enum! { - StructureBlockMode: VarInt { - Save = 0, - Load = 1, - Corner = 2, - Data = 3, - } - } - - def_enum! { - StructureBlockMirror: VarInt { - None = 0, - LeftRight = 1, - FrontBack = 2, - } - } - - def_enum! { - StructureBlockRotation: VarInt { - None = 0, - Clockwise90 = 1, - Clockwise180 = 2, - Counterclockwise90 = 3, - } - } - - def_bitfield! { - StructureBlockFlags: i8 { - ignore_entities = 0, - show_air = 1, - show_bounding_box = 2, - } - } - - def_struct! { - UpdateSign 0x2b { - location: BlockPos, - lines: [BoundedString<0, 384>; 4], - } - } - - def_struct! { - PlayerArmSwing 0x2c { - hand: Hand, - } - } - - def_struct! { - Spectate 0x2d { - target: Uuid, - } - } - - def_struct! { - PlayerBlockPlacement 0x2e { - hand: Hand, - location: BlockPos, - face: BlockFace, - cursor_pos: Vec3, - head_inside_block: bool, - } - } - - def_struct! { - UseItem 0x2f { - hand: Hand, - } - } - - macro_rules! def_server_play_packet_enum { + def_enum! { + StructureBlockMode: VarInt { + Save = 0, + Load = 1, + Corner = 2, + Data = 3, + } + } + + def_enum! { + StructureBlockMirror: VarInt { + None = 0, + LeftRight = 1, + FrontBack = 2, + } + } + + def_enum! { + StructureBlockRotation: VarInt { + None = 0, + Clockwise90 = 1, + Clockwise180 = 2, + Counterclockwise90 = 3, + } + } + + def_bitfield! { + StructureBlockFlags: i8 { + ignore_entities = 0, + show_air = 1, + show_bounding_box = 2, + } + } + + def_struct! { + UpdateSign 0x2b { + location: BlockPos, + lines: [BoundedString<0, 384>; 4], + } + } + + def_struct! { + PlayerArmSwing 0x2c { + hand: Hand, + } + } + + def_struct! { + Spectate 0x2d { + target: Uuid, + } + } + + def_struct! { + PlayerBlockPlacement 0x2e { + hand: Hand, + location: BlockPos, + face: BlockFace, + cursor_pos: Vec3, + head_inside_block: bool, + } + } + + def_struct! { + UseItem 0x2f { + hand: Hand, + } + } + + macro_rules! def_c2s_play_packet_enum { { $($packet:ident),* $(,)? } => { - /// An enum of all serverbound play packets. + /// An enum of all client-to-server play packets. #[derive(Clone, Debug)] - pub enum ServerPlayPacket { + pub enum C2sPlayPacket { $($packet($packet)),* } - impl private::Sealed for ServerPlayPacket {} + impl private::Sealed for C2sPlayPacket {} - impl DecodePacket for ServerPlayPacket { - fn decode_packet(r: &mut impl Read) -> anyhow::Result { - let packet_id = VarInt::decode(r).context("failed to read play packet ID")?.0; + impl DecodePacket for C2sPlayPacket { + fn decode_packet(r: &mut impl Read) -> anyhow::Result { + let packet_id = VarInt::decode(r).context("failed to read c2s play packet ID")?.0; match packet_id { $( $packet::PACKET_ID => { let pkt = $packet::decode(r)?; - Ok(ServerPlayPacket::$packet(pkt)) + Ok(C2sPlayPacket::$packet(pkt)) } )* - id => bail!("unknown play packet ID {:#04x}", id) + id => bail!("unknown c2s play packet ID {:#04x}", id) } } } @@ -1767,7 +1771,7 @@ pub mod play { #[cfg(test)] #[test] - fn test_server_play_packet_order() { + fn test_c2s_play_packet_order() { let ids = [ $( (stringify!($packet), $packet::PACKET_ID), @@ -1775,61 +1779,62 @@ pub mod play { ]; if let Some(w) = ids.windows(2).find(|w| w[0].1 >= w[1].1) { - panic!("the {} and {} variants of the server play packet enum are not properly sorted by their packet ID", w[0].0, w[1].0); + panic!("the {} and {} variants of the c2s play packet enum are not properly sorted by their packet ID", w[0].0, w[1].0); } } } } - def_server_play_packet_enum! { - TeleportConfirm, - QueryBlockNbt, - SetDifficulty, - ChatMessageServerbound, - ClientStatus, - ClientSettings, - TabCompleteServerbound, - ClickWindowButton, - ClickWindow, - CloseWindow, - PluginMessageServerbound, - EditBook, - QueryEntityNbt, - InteractEntity, - GenerateStructure, - KeepAliveServerbound, - LockDifficulty, - PlayerPosition, - PlayerPositionAndRotation, - PlayerRotation, - PlayerMovement, - VehicleMoveServerbound, - SteerBoat, - PickItem, - CraftRecipeRequest, - PlayerAbilitiesServerbound, - PlayerDigging, - EntityAction, - SteerVehicle, - Pong, - SetRecipeBookState, - SetDisplayedRecipe, - NameItem, - ResourcePackStatus, - AdvancementTab, - SelectTrade, - SetBeaconEffect, - HeldItemChangeServerbound, - UpdateCommandBlock, - UpdateCommandBlockMinecart, - CreativeInventoryAction, - UpdateJigsawBlock, - UpdateStructureBlock, - UpdateSign, - PlayerArmSwing, - Spectate, - PlayerBlockPlacement, - UseItem, + def_c2s_play_packet_enum! { + TeleportConfirm, + QueryBlockNbt, + SetDifficulty, + ChatMessageServerbound, + ClientStatus, + ClientSettings, + TabCompleteServerbound, + ClickWindowButton, + ClickWindow, + CloseWindow, + PluginMessageServerbound, + EditBook, + QueryEntityNbt, + InteractEntity, + GenerateStructure, + KeepAliveServerbound, + LockDifficulty, + PlayerPosition, + PlayerPositionAndRotation, + PlayerRotation, + PlayerMovement, + VehicleMoveServerbound, + SteerBoat, + PickItem, + CraftRecipeRequest, + PlayerAbilitiesServerbound, + PlayerDigging, + EntityAction, + SteerVehicle, + Pong, + SetRecipeBookState, + SetDisplayedRecipe, + NameItem, + ResourcePackStatus, + AdvancementTab, + SelectTrade, + SetBeaconEffect, + HeldItemChangeServerbound, + UpdateCommandBlock, + UpdateCommandBlockMinecart, + CreativeInventoryAction, + UpdateJigsawBlock, + UpdateStructureBlock, + UpdateSign, + PlayerArmSwing, + Spectate, + PlayerBlockPlacement, + UseItem, + } } } diff --git a/src/server.rs b/src/server.rs index bb5b0ff..52c75be 100644 --- a/src/server.rs +++ b/src/server.rs @@ -27,18 +27,23 @@ use tokio::sync::{oneshot, Semaphore}; use uuid::Uuid; use crate::codec::{Decoder, Encoder}; -use crate::config::{Biome, BiomeId, Config, Dimension, DimensionId, ServerListPing}; +use crate::config::{Config, ServerListPing}; use crate::packets::handshake::{Handshake, HandshakeNextState}; -use crate::packets::login::{ - self, EncryptionRequest, EncryptionResponse, LoginStart, LoginSuccess, SetCompression, -}; -use crate::packets::play::{ClientPlayPacket, ServerPlayPacket}; -use crate::packets::status::{Ping, Pong, Request, Response}; +use crate::packets::login; +use crate::packets::login::c2s::{EncryptionResponse, LoginStart}; +use crate::packets::login::s2c::{EncryptionRequest, LoginSuccess, SetCompression}; +use crate::packets::play::c2s::C2sPlayPacket; +use crate::packets::play::s2c::S2cPlayPacket; +use crate::packets::status::c2s::{Ping, Request}; +use crate::packets::status::s2c::{Pong, Response}; use crate::protocol::{BoundedArray, BoundedString}; use crate::util::valid_username; use crate::var_int::VarInt; use crate::world::Worlds; -use crate::{Client, ClientMut, Ticks, WorldsMut, PROTOCOL_VERSION, VERSION_NAME}; +use crate::{ + Biome, BiomeId, Client, ClientMut, Dimension, DimensionId, Ticks, WorldsMut, PROTOCOL_VERSION, + VERSION_NAME, +}; /// A handle to a running Minecraft server containing state which is accessible /// outside the update loop. Servers are internally refcounted and can be shared @@ -89,7 +94,7 @@ pub struct NewClientData { struct NewClientMessage { ncd: NewClientData, - reply: oneshot::Sender, + reply: oneshot::Sender, } /// The result type returned from [`ServerConfig::start`] after the server is @@ -97,8 +102,8 @@ struct NewClientMessage { pub type ShutdownResult = Result<(), ShutdownError>; pub type ShutdownError = Box; -pub(crate) type ClientPacketChannels = (Sender, Receiver); -pub(crate) type ServerPacketChannels = (Sender, Receiver); +pub(crate) type S2cPacketChannels = (Sender, Receiver); +pub(crate) type C2sPacketChannels = (Sender, Receiver); impl Server { pub fn config(&self) -> &(impl Config + ?Sized) { @@ -368,12 +373,7 @@ fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult { }); world.clients.par_iter_mut().for_each(|(_, mut client)| { - client.update( - &server, - &world.entities, - &world.chunks, - &world.meta, - ); + client.update(&server, &world.entities, &world.chunks, &world.meta); }); world.entities.update(); @@ -396,8 +396,8 @@ fn join_player(server: &Server, mut worlds: WorldsMut, msg: NewClientMessage) { let (clientbound_tx, clientbound_rx) = flume::bounded(server.0.outgoing_packet_capacity); let (serverbound_tx, serverbound_rx) = flume::bounded(server.0.incoming_packet_capacity); - let client_packet_channels: ClientPacketChannels = (serverbound_tx, clientbound_rx); - let server_packet_channels: ServerPacketChannels = (clientbound_tx, serverbound_rx); + let client_packet_channels: S2cPacketChannels = (serverbound_tx, clientbound_rx); + let server_packet_channels: C2sPacketChannels = (clientbound_tx, serverbound_rx); let _ = msg.reply.send(client_packet_channels); @@ -674,7 +674,7 @@ async fn handle_login( if let Err(reason) = server.0.cfg.login(server, &npd).await { log::info!("Disconnect at login: \"{reason}\""); - c.0.write_packet(&login::Disconnect { reason }).await?; + c.0.write_packet(&login::s2c::Disconnect { reason }).await?; return Ok(None); } diff --git a/src/slotmap.rs b/src/slotmap.rs index 9d6adaf..0b22be1 100644 --- a/src/slotmap.rs +++ b/src/slotmap.rs @@ -93,7 +93,7 @@ impl SlotMap { value: f(key), version: key.version(), }); - + let value = match self.slots.last_mut() { Some(Slot::Occupied { value, .. }) => value, _ => unreachable!(), diff --git a/src/text.rs b/src/text.rs index d784fe8..f82c897 100644 --- a/src/text.rs +++ b/src/text.rs @@ -9,7 +9,7 @@ use serde::de::Visitor; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::protocol::{BoundedString, Decode, Encode}; -use crate::Identifier; +use crate::Ident; /// Represents formatted text in Minecraft's JSON text format. /// @@ -308,14 +308,14 @@ enum ClickEvent { enum HoverEvent { ShowText(Box), ShowItem { - id: Identifier, + id: Ident, count: Option, // TODO: tag }, ShowEntity { name: Box, #[serde(rename = "type")] - typ: Identifier, + typ: Ident, // TODO: id (hyphenated entity UUID as a string) }, } diff --git a/src/util.rs b/src/util.rs index 7d8ef58..e837027 100644 --- a/src/util.rs +++ b/src/util.rs @@ -54,8 +54,9 @@ where aabb } -/// Takes a normalized direction vector and returns a (yaw, pitch) tuple in degrees. -/// +/// Takes a normalized direction vector and returns a (yaw, pitch) tuple in +/// degrees. +/// /// This function is the inverse of [`from_yaw_and_pitch`]. pub fn to_yaw_and_pitch(d: Vec3) -> (f32, f32) { debug_assert!(d.is_normalized(), "the given vector should be normalized"); @@ -65,9 +66,10 @@ pub fn to_yaw_and_pitch(d: Vec3) -> (f32, f32) { (yaw, pitch) } -/// Takes yaw and pitch angles (in degrees) and returns a normalized direction vector. -/// +/// Takes yaw and pitch angles (in degrees) and returns a normalized direction +/// vector. +/// /// This function is the inverse of [`to_yaw_and_pitch`]. pub fn from_yaw_and_pitch(yaw: f32, pitch: f32) -> Vec3 { todo!() -} \ No newline at end of file +} diff --git a/src/world.rs b/src/world.rs index ec5b16d..cb207b9 100644 --- a/src/world.rs +++ b/src/world.rs @@ -3,9 +3,8 @@ use std::ops::Deref; use rayon::iter::ParallelIterator; -use crate::config::DimensionId; use crate::slotmap::{Key, SlotMap}; -use crate::{Chunks, ChunksMut, Clients, ClientsMut, Entities, EntitiesMut, Server}; +use crate::{Chunks, ChunksMut, Clients, ClientsMut, DimensionId, Entities, EntitiesMut, Server}; pub struct Worlds { sm: SlotMap,