mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-27 05:56:33 +11:00
Move protocol code to valence_protocol
+ redesigns (#153)
Closes #83 This PR aims to move all of Valence's networking code to the new `valence_protocol` crate. Anything not specific to valence is going in the new crate. It also redesigns the way packets are defined and makes a huge number of small additions and improvements. It should be much easier to see where code is supposed to go from now on. `valence_protocol` is a new library which enables interactions with Minecraft's protocol. It is completely decoupled from valence and can be used to build new clients, servers, tools, etc. There are two additions that will help with #5 especially: - It is now easy to define new packets or modifications of existing packets. Not all packets need to be bidirectional. - The `CachedEncode` type has been created. This is used to safely cache redundant calls to `Encode::encode`.
This commit is contained in:
parent
9f1c946839
commit
420f2d1b7c
68 changed files with 4850 additions and 4109 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -12,18 +12,13 @@ build = "build/main.rs"
|
|||
authors = ["Ryan Johnson <ryanj00a@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
aes = "0.7.5"
|
||||
anyhow = "1.0.65"
|
||||
approx = "0.5.1"
|
||||
arrayvec = "0.7.2"
|
||||
async-trait = "0.1.57"
|
||||
base64 = "0.13.0"
|
||||
bitfield-struct = "0.1.7"
|
||||
bitvec = "1.0.1"
|
||||
byteorder = "1.4.3"
|
||||
bytes = "1.2.1"
|
||||
cfb8 = "0.7.1"
|
||||
flate2 = "1.0.24"
|
||||
flume = "0.10.14"
|
||||
futures = "0.3.24"
|
||||
hmac = "0.12.1"
|
||||
|
@ -41,7 +36,8 @@ sha2 = "0.10.6"
|
|||
thiserror = "1.0.35"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
uuid = { version = "1.1.2", features = ["serde"] }
|
||||
valence_nbt = "0.4.0"
|
||||
valence_nbt = { version = "0.4.0", path = "valence_nbt" }
|
||||
valence_protocol = { version = "0.1.0", path = "valence_protocol", features = ["encryption", "compression"] }
|
||||
vek = "0.15.8"
|
||||
|
||||
[dependencies.tokio]
|
||||
|
@ -70,7 +66,9 @@ num = "0.4.0"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"valence_derive",
|
||||
"valence_nbt",
|
||||
"valence_protocol",
|
||||
"packet_inspector",
|
||||
"performance_tests/players"
|
||||
]
|
||||
|
|
|
@ -359,8 +359,8 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
|||
quote! {
|
||||
if self.#field_name != (#default_expr) {
|
||||
data.push(#field_index);
|
||||
VarInt(#type_id).encode(data).unwrap();
|
||||
#encodable.encode(data).unwrap();
|
||||
VarInt(#type_id).encode(&mut *data).unwrap();
|
||||
#encodable.encode(&mut *data).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -374,8 +374,8 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
|||
quote! {
|
||||
if (self.__modified_flags >> #field_index as #modified_flags_type) & 1 == 1 {
|
||||
data.push(#field_index);
|
||||
VarInt(#type_id).encode(data).unwrap();
|
||||
#encodable.encode(data).unwrap();
|
||||
VarInt(#type_id).encode(&mut *data).unwrap();
|
||||
#encodable.encode(&mut *data).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,11 +5,8 @@ use std::{env, fs};
|
|||
use anyhow::Context;
|
||||
use proc_macro2::{Ident, Span};
|
||||
|
||||
mod block;
|
||||
mod enchant;
|
||||
mod entity;
|
||||
mod entity_event;
|
||||
mod item;
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
println!("cargo:rerun-if-changed=extracted/");
|
||||
|
@ -17,9 +14,6 @@ pub fn main() -> anyhow::Result<()> {
|
|||
let generators = [
|
||||
(entity::build as fn() -> _, "entity.rs"),
|
||||
(entity_event::build, "entity_event.rs"),
|
||||
(block::build, "block.rs"),
|
||||
(item::build, "item.rs"),
|
||||
(enchant::build, "enchant.rs"),
|
||||
];
|
||||
|
||||
let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?;
|
||||
|
@ -27,7 +21,7 @@ pub fn main() -> anyhow::Result<()> {
|
|||
for (g, file_name) in generators {
|
||||
let path = Path::new(&out_dir).join(file_name);
|
||||
let code = g()?.to_string();
|
||||
fs::write(&path, &code)?;
|
||||
fs::write(&path, code)?;
|
||||
|
||||
// Format the output for debugging purposes.
|
||||
// Doesn't matter if rustfmt is unavailable.
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
|
||||
use log::LevelFilter;
|
||||
use num::Integer;
|
||||
use valence::client::{DiggingStatus, Hand};
|
||||
use valence::client::DiggingStatus;
|
||||
use valence::prelude::*;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
|
|
|
@ -3,9 +3,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
|
||||
use log::LevelFilter;
|
||||
use num::Integer;
|
||||
use valence::client::Hand;
|
||||
use valence::prelude::*;
|
||||
use valence::protocol::{SlotId, VarInt};
|
||||
use valence_protocol::var_int::VarInt;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
|
|
@ -2,7 +2,6 @@ use std::net::SocketAddr;
|
|||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use log::LevelFilter;
|
||||
use valence::client::InteractWithEntityKind;
|
||||
use valence::prelude::*;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
|
@ -203,11 +202,7 @@ impl Config for Game {
|
|||
Some(ClientEvent::StopSprinting) => {
|
||||
client.state.extra_knockback = false;
|
||||
}
|
||||
Some(ClientEvent::InteractWithEntity {
|
||||
id,
|
||||
kind: InteractWithEntityKind::Attack,
|
||||
..
|
||||
}) => {
|
||||
Some(ClientEvent::InteractWithEntity { id, .. }) => {
|
||||
if let Some(target) = server.entities.get_mut(id) {
|
||||
if !target.state.attacked
|
||||
&& current_tick - target.state.last_attack_time >= 10
|
||||
|
|
|
@ -5,11 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
use log::LevelFilter;
|
||||
use num::Integer;
|
||||
use rayon::prelude::*;
|
||||
use valence::client::Hand;
|
||||
use valence::entity::types::Pose;
|
||||
use valence::prelude::*;
|
||||
// TODO: re-export this somewhere in valence.
|
||||
use valence::protocol::packets::s2c::play::SoundCategory;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
@ -192,9 +188,9 @@ impl Config for Game {
|
|||
|
||||
if !server.state.board[index] {
|
||||
client.play_sound(
|
||||
ident!("minecraft:block.note_block.banjo"),
|
||||
Ident::new("minecraft:block.note_block.banjo").unwrap(),
|
||||
SoundCategory::Block,
|
||||
Vec3::<i32>::from(position).as_(),
|
||||
Vec3::new(position.x, position.y, position.z).as_(),
|
||||
0.5f32,
|
||||
1f32,
|
||||
);
|
||||
|
@ -217,7 +213,7 @@ impl Config for Game {
|
|||
if sneaking != server.state.paused {
|
||||
server.state.paused = sneaking;
|
||||
client.play_sound(
|
||||
ident!("block.note_block.pling"),
|
||||
Ident::new("block.note_block.pling").unwrap(),
|
||||
SoundCategory::Block,
|
||||
client.position(),
|
||||
0.5f32,
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::net::SocketAddr;
|
|||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use log::LevelFilter;
|
||||
use valence::entity::types::{Facing, PaintingKind, Pose};
|
||||
use valence::prelude::*;
|
||||
use valence_protocol::entity_meta::{Facing, PaintingKind};
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
|
|
@ -4,10 +4,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
use log::LevelFilter;
|
||||
use num::Integer;
|
||||
pub use valence::prelude::*;
|
||||
// TODO: remove protocol imports.
|
||||
use valence::protocol::packets::c2s::play::ClickContainerMode;
|
||||
use valence::protocol::packets::s2c::play::SoundCategory;
|
||||
use valence::protocol::SlotId;
|
||||
use valence_protocol::types::ClickContainerMode;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
@ -220,7 +217,7 @@ fn play_note(client: &mut Client<Game>, player: &mut Entity<Game>, clicked_slot:
|
|||
+ PITCH_MIN;
|
||||
client.send_message(format!("playing note with pitch: {}", pitch));
|
||||
client.play_sound(
|
||||
ident!("block.note_block.harp"),
|
||||
Ident::new("block.note_block.harp").unwrap(),
|
||||
SoundCategory::Block,
|
||||
player.position(),
|
||||
10.0,
|
||||
|
|
|
@ -6,10 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
use log::LevelFilter;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use valence::client::SetTitleAnimationTimes;
|
||||
use valence::prelude::*;
|
||||
// TODO: remove protocol imports.
|
||||
use valence::protocol::packets::s2c::play::SoundCategory;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
@ -238,7 +235,7 @@ impl Config for Game {
|
|||
}
|
||||
|
||||
client.play_sound(
|
||||
ident!("minecraft:block.note_block.bass"),
|
||||
Ident::new("minecraft:block.note_block.bass").unwrap(),
|
||||
SoundCategory::Master,
|
||||
client.position(),
|
||||
1f32,
|
||||
|
|
|
@ -2,8 +2,9 @@ use std::net::SocketAddr;
|
|||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use log::LevelFilter;
|
||||
use valence::client::{InteractWithEntityKind, ResourcePackStatus};
|
||||
use valence::prelude::*;
|
||||
use valence_protocol::packets::c2s::play::ResourcePackC2s;
|
||||
use valence_protocol::types::EntityInteraction;
|
||||
|
||||
pub fn main() -> ShutdownResult {
|
||||
env_logger::Builder::new()
|
||||
|
@ -160,8 +161,8 @@ impl Config for Game {
|
|||
|
||||
while let Some(event) = handle_event_default(client, player) {
|
||||
match event {
|
||||
ClientEvent::InteractWithEntity { kind, id, .. } => {
|
||||
if kind == InteractWithEntityKind::Attack
|
||||
ClientEvent::InteractWithEntity { id, interact, .. } => {
|
||||
if interact == EntityInteraction::Attack
|
||||
&& Some(id) == server.state.sheep_id
|
||||
{
|
||||
set_example_pack(client);
|
||||
|
@ -169,13 +170,13 @@ impl Config for Game {
|
|||
}
|
||||
ClientEvent::ResourcePackStatusChanged(s) => {
|
||||
let message = match s {
|
||||
ResourcePackStatus::SuccessfullyLoaded => {
|
||||
ResourcePackC2s::SuccessfullyLoaded => {
|
||||
"The resource pack was successfully loaded!".color(Color::GREEN)
|
||||
}
|
||||
ResourcePackStatus::Declined => {
|
||||
ResourcePackC2s::Declined => {
|
||||
"You declined the resource pack :(".color(Color::RED)
|
||||
}
|
||||
ResourcePackStatus::FailedDownload => {
|
||||
ResourcePackC2s::FailedDownload => {
|
||||
"The resource pack download failed.".color(Color::RED)
|
||||
}
|
||||
_ => continue,
|
||||
|
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
description = "A simple Minecraft proxy for inspecting packets."
|
||||
|
||||
[dependencies]
|
||||
valence = { path = ".." }
|
||||
valence_protocol = { path = "../valence_protocol", version = "0.1.0", features = ["compression"] }
|
||||
clap = { version = "3.2.8", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
anyhow = "1"
|
||||
|
|
|
@ -15,15 +15,16 @@ use tokio::net::{TcpListener, TcpStream};
|
|||
use tokio::sync::Semaphore;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::timeout;
|
||||
use valence::protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use valence::protocol::packets::c2s::handshake::{Handshake, HandshakeNextState};
|
||||
use valence::protocol::packets::c2s::login::{EncryptionResponse, LoginStart};
|
||||
use valence::protocol::packets::c2s::play::C2sPlayPacket;
|
||||
use valence::protocol::packets::c2s::status::{PingRequest, StatusRequest};
|
||||
use valence::protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket};
|
||||
use valence::protocol::packets::s2c::play::S2cPlayPacket;
|
||||
use valence::protocol::packets::s2c::status::{PingResponse, StatusResponse};
|
||||
use valence::protocol::packets::{DecodePacket, EncodePacket, PacketName};
|
||||
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use valence_protocol::packets::c2s::handshake::Handshake;
|
||||
use valence_protocol::packets::c2s::login::{EncryptionResponse, LoginStart};
|
||||
use valence_protocol::packets::c2s::play::C2sPlayPacket;
|
||||
use valence_protocol::packets::c2s::status::{PingRequest, StatusRequest};
|
||||
use valence_protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket};
|
||||
use valence_protocol::packets::s2c::play::S2cPlayPacket;
|
||||
use valence_protocol::packets::s2c::status::{PingResponse, StatusResponse};
|
||||
use valence_protocol::types::HandshakeNextState;
|
||||
use valence_protocol::{Decode, Encode, Packet};
|
||||
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
#[clap(author, version, about)]
|
||||
|
@ -59,22 +60,12 @@ struct State {
|
|||
const TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
impl State {
|
||||
pub async fn rw_packet<P>(&mut self) -> anyhow::Result<P>
|
||||
pub async fn rw_packet<'a, P>(&'a mut self) -> anyhow::Result<P>
|
||||
where
|
||||
P: DecodePacket + EncodePacket,
|
||||
P: Decode<'a> + Encode + Packet + fmt::Debug,
|
||||
{
|
||||
timeout(TIMEOUT, async {
|
||||
loop {
|
||||
if let Some(pkt) = self.dec.try_next_packet()? {
|
||||
self.enc.append_packet(&pkt)?;
|
||||
|
||||
let bytes = self.enc.take();
|
||||
self.write.write_all(&bytes).await?;
|
||||
|
||||
self.print(&pkt);
|
||||
return Ok(pkt);
|
||||
}
|
||||
|
||||
while !self.dec.has_next_packet()? {
|
||||
self.dec.reserve(4096);
|
||||
let mut buf = self.dec.take_capacity();
|
||||
|
||||
|
@ -84,27 +75,31 @@ impl State {
|
|||
|
||||
self.dec.queue_bytes(buf);
|
||||
}
|
||||
|
||||
let pkt: P = self.dec.try_next_packet()?.unwrap();
|
||||
|
||||
self.enc.append_packet(&pkt)?;
|
||||
|
||||
let bytes = self.enc.take();
|
||||
self.write.write_all(&bytes).await?;
|
||||
|
||||
if let Some(r) = &self.cli.regex {
|
||||
if !r.is_match(pkt.packet_name()) {
|
||||
return Ok(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
if self.cli.timestamp {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
println!("{now} {pkt:#?}");
|
||||
} else {
|
||||
println!("{pkt:#?}");
|
||||
}
|
||||
|
||||
Ok(pkt)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
fn print<P>(&self, pkt: &P)
|
||||
where
|
||||
P: fmt::Debug + PacketName + ?Sized,
|
||||
{
|
||||
if let Some(r) = &self.cli.regex {
|
||||
if !r.is_match(pkt.packet_name()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.cli.timestamp {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
println!("{now} {pkt:#?}");
|
||||
} else {
|
||||
println!("{pkt:#?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
|
@ -4,9 +4,8 @@ use std::collections::HashSet;
|
|||
|
||||
use anyhow::ensure;
|
||||
use valence_nbt::{compound, Compound};
|
||||
|
||||
use crate::ident;
|
||||
use crate::ident::Ident;
|
||||
use valence_protocol::ident;
|
||||
use valence_protocol::ident::Ident;
|
||||
|
||||
/// Identifies a particular [`Biome`] on the server.
|
||||
///
|
||||
|
|
86
src/chunk.rs
86
src/chunk.rs
|
@ -11,20 +11,21 @@ use std::collections::HashMap;
|
|||
use std::io::Write;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use paletted_container::PalettedContainer;
|
||||
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use valence_nbt::compound;
|
||||
|
||||
use crate::biome::BiomeId;
|
||||
use crate::block::BlockState;
|
||||
use crate::block_pos::BlockPos;
|
||||
pub use crate::chunk_pos::ChunkPos;
|
||||
use crate::config::Config;
|
||||
use crate::protocol::packets::s2c::play::{
|
||||
use valence_protocol::block::BlockState;
|
||||
use valence_protocol::block_pos::BlockPos;
|
||||
use valence_protocol::packets::s2c::play::{
|
||||
BlockUpdate, ChunkDataAndUpdateLight, UpdateSectionBlocks,
|
||||
};
|
||||
use crate::protocol::{Encode, VarInt, VarLong};
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use valence_protocol::var_long::VarLong;
|
||||
use valence_protocol::Encode;
|
||||
|
||||
use crate::biome::BiomeId;
|
||||
pub use crate::chunk_pos::ChunkPos;
|
||||
use crate::config::Config;
|
||||
use crate::server::PlayPacketController;
|
||||
use crate::util::bits_needed;
|
||||
|
||||
|
@ -540,61 +541,56 @@ impl<C: Config> LoadedChunk<C> {
|
|||
self.created_this_tick
|
||||
}
|
||||
|
||||
/// Gets the chunk data packet for this chunk with the given position.
|
||||
/// Queues the chunk data packet for this chunk with the given position.
|
||||
pub(crate) fn chunk_data_packet(
|
||||
&self,
|
||||
ctrl: &mut PlayPacketController,
|
||||
scratch: &mut Vec<u8>,
|
||||
pos: ChunkPos,
|
||||
biome_registry_len: usize,
|
||||
) -> ChunkDataAndUpdateLight {
|
||||
let mut blocks_and_biomes = Vec::new();
|
||||
) -> anyhow::Result<()> {
|
||||
scratch.clear();
|
||||
|
||||
for sect in self.sections.iter() {
|
||||
sect.non_air_count.encode(&mut blocks_and_biomes).unwrap();
|
||||
sect.non_air_count.encode(&mut *scratch)?;
|
||||
|
||||
sect.block_states
|
||||
.encode_mc_format(
|
||||
&mut blocks_and_biomes,
|
||||
|b| b.to_raw().into(),
|
||||
4,
|
||||
8,
|
||||
bits_needed(BlockState::max_raw().into()),
|
||||
)
|
||||
.unwrap();
|
||||
sect.block_states.encode_mc_format(
|
||||
&mut *scratch,
|
||||
|b| b.to_raw().into(),
|
||||
4,
|
||||
8,
|
||||
bits_needed(BlockState::max_raw().into()),
|
||||
)?;
|
||||
|
||||
sect.biomes
|
||||
.encode_mc_format(
|
||||
&mut blocks_and_biomes,
|
||||
|b| b.0.into(),
|
||||
0,
|
||||
3,
|
||||
bits_needed(biome_registry_len - 1),
|
||||
)
|
||||
.unwrap();
|
||||
sect.biomes.encode_mc_format(
|
||||
&mut *scratch,
|
||||
|b| b.0.into(),
|
||||
0,
|
||||
3,
|
||||
bits_needed(biome_registry_len - 1),
|
||||
)?;
|
||||
}
|
||||
|
||||
ChunkDataAndUpdateLight {
|
||||
ctrl.append_packet(&ChunkDataAndUpdateLight {
|
||||
chunk_x: pos.x,
|
||||
chunk_z: pos.z,
|
||||
heightmaps: compound! {
|
||||
// TODO: placeholder heightmap.
|
||||
"MOTION_BLOCKING" => vec![0_i64; 37],
|
||||
},
|
||||
blocks_and_biomes,
|
||||
blocks_and_biomes: scratch,
|
||||
block_entities: vec![], // TODO
|
||||
trust_edges: true,
|
||||
// sky_light_mask: bitvec![u64, _; 1; section_count + 2],
|
||||
sky_light_mask: BitVec::new(),
|
||||
block_light_mask: BitVec::new(),
|
||||
empty_sky_light_mask: BitVec::new(),
|
||||
empty_block_light_mask: BitVec::new(),
|
||||
// sky_light_arrays: vec![[0xff; 2048]; section_count + 2],
|
||||
sky_light_mask: Vec::new(),
|
||||
block_light_mask: Vec::new(),
|
||||
empty_sky_light_mask: Vec::new(),
|
||||
empty_block_light_mask: Vec::new(),
|
||||
sky_light_arrays: vec![],
|
||||
block_light_arrays: vec![],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns changes to this chunk as block change packets through the
|
||||
/// provided closure.
|
||||
/// Queues block change packets for this chunk.
|
||||
pub(crate) fn block_change_packets(
|
||||
&self,
|
||||
pos: ChunkPos,
|
||||
|
@ -786,7 +782,7 @@ fn compact_u64s_len(vals_count: usize, bits_per_val: usize) -> usize {
|
|||
|
||||
#[inline]
|
||||
fn encode_compact_u64s(
|
||||
w: &mut impl Write,
|
||||
mut w: impl Write,
|
||||
mut vals: impl Iterator<Item = u64>,
|
||||
bits_per_val: usize,
|
||||
) -> anyhow::Result<()> {
|
||||
|
@ -802,11 +798,11 @@ fn encode_compact_u64s(
|
|||
debug_assert!(val < 2_u128.pow(bits_per_val as _) as _);
|
||||
n |= val << (i * bits_per_val);
|
||||
}
|
||||
None if i > 0 => return n.encode(w),
|
||||
None if i > 0 => return n.encode(&mut w),
|
||||
None => return Ok(()),
|
||||
}
|
||||
}
|
||||
n.encode(w)?;
|
||||
n.encode(&mut w)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@ use std::array;
|
|||
use std::io::Write;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use valence_protocol::Encode;
|
||||
|
||||
use crate::chunk::{compact_u64s_len, encode_compact_u64s};
|
||||
use crate::protocol::{Encode, VarInt};
|
||||
use crate::util::bits_needed;
|
||||
|
||||
/// `HALF_LEN` must be equal to `ceil(LEN / 2)`.
|
||||
|
@ -175,7 +176,7 @@ impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize>
|
|||
VarInt(to_bits(*val) as i32).encode(&mut writer)?;
|
||||
|
||||
// Number of longs
|
||||
VarInt(0).encode(&mut writer)?;
|
||||
VarInt(0).encode(writer)?;
|
||||
}
|
||||
Self::Indirect(ind) => {
|
||||
let bits_per_entry = min_indirect_bits.max(bits_needed(ind.palette.len() - 1));
|
||||
|
@ -189,7 +190,7 @@ impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize>
|
|||
VarInt(compact_u64s_len(LEN, direct_bits) as _).encode(&mut writer)?;
|
||||
// Data array
|
||||
encode_compact_u64s(
|
||||
&mut writer,
|
||||
writer,
|
||||
(0..LEN).map(|i| to_bits(ind.get(i))),
|
||||
direct_bits,
|
||||
)?;
|
||||
|
@ -208,7 +209,7 @@ impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize>
|
|||
VarInt(compact_u64s_len(LEN, bits_per_entry) as _).encode(&mut writer)?;
|
||||
// Data array
|
||||
encode_compact_u64s(
|
||||
&mut writer,
|
||||
writer,
|
||||
ind.indices
|
||||
.iter()
|
||||
.cloned()
|
||||
|
@ -226,7 +227,7 @@ impl<T: Copy + Eq + Default, const LEN: usize, const HALF_LEN: usize>
|
|||
// Number of longs in data array.
|
||||
VarInt(compact_u64s_len(LEN, direct_bits) as _).encode(&mut writer)?;
|
||||
// Data array
|
||||
encode_compact_u64s(&mut writer, dir.iter().cloned().map(to_bits), direct_bits)?;
|
||||
encode_compact_u64s(writer, dir.iter().cloned().map(to_bits), direct_bits)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::block::BlockPos;
|
||||
use valence_protocol::block_pos::BlockPos;
|
||||
|
||||
/// The X and Z position of a chunk in a world.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
|
|
268
src/client.rs
268
src/client.rs
|
@ -10,9 +10,33 @@ pub use bitfield_struct::bitfield;
|
|||
pub use event::*;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use uuid::Uuid;
|
||||
use valence_protocol::block_pos::BlockPos;
|
||||
use valence_protocol::byte_angle::ByteAngle;
|
||||
use valence_protocol::ident::Ident;
|
||||
use valence_protocol::item::ItemStack;
|
||||
use valence_protocol::packets::c2s::play::ClientCommand;
|
||||
use valence_protocol::packets::s2c::play::{
|
||||
AcknowledgeBlockChange, ClearTitles, CombatDeath, CustomSoundEffect, DisconnectPlay,
|
||||
EntityAnimationS2c, EntityEvent, GameEvent, KeepAliveS2c, LoginPlayOwned, OpenScreen,
|
||||
PluginMessageS2c, RemoveEntities, ResourcePackS2c, RespawnOwned, SetActionBarText,
|
||||
SetCenterChunk, SetContainerContent, SetDefaultSpawnPosition, SetEntityMetadata,
|
||||
SetEntityVelocity, SetExperience, SetHeadRotation, SetHealth, SetRenderDistance,
|
||||
SetSubtitleText, SetTitleAnimationTimes, SetTitleText, SynchronizePlayerPosition,
|
||||
SystemChatMessage, TeleportEntity, UnloadChunk, UpdateAttributes, UpdateEntityPosition,
|
||||
UpdateEntityPositionAndRotation, UpdateEntityRotation, UpdateTime,
|
||||
};
|
||||
use valence_protocol::packets::C2sPlayPacket;
|
||||
use valence_protocol::raw_bytes::RawBytes;
|
||||
use valence_protocol::text::Text;
|
||||
use valence_protocol::types::{
|
||||
Action, AttributeProperty, GameMode, GameStateChangeReason, SoundCategory,
|
||||
SyncPlayerPosLookFlags,
|
||||
};
|
||||
use valence_protocol::username::Username;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use valence_protocol::{ident, types, Encode, Packet};
|
||||
use vek::Vec3;
|
||||
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::chunk_pos::ChunkPos;
|
||||
use crate::config::Config;
|
||||
use crate::dimension::DimensionId;
|
||||
|
@ -20,37 +44,17 @@ use crate::entity::data::Player;
|
|||
use crate::entity::{
|
||||
self, velocity_to_packet_units, Entities, EntityId, EntityKind, StatusOrAnimation,
|
||||
};
|
||||
use crate::ident::Ident;
|
||||
use crate::inventory::{
|
||||
Inventories, Inventory, InventoryDirtyable, InventoryError, InventoryId, PlayerInventory,
|
||||
WindowInventory,
|
||||
SlotId, WindowInventory,
|
||||
};
|
||||
use crate::item::ItemStack;
|
||||
use crate::player_list::{PlayerListId, PlayerLists};
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::packets::c2s::play::{
|
||||
self, C2sPlayPacket, ClientCommand, InteractKind, PlayerCommandId,
|
||||
};
|
||||
pub use crate::protocol::packets::s2c::play::SetTitleAnimationTimes;
|
||||
use crate::protocol::packets::s2c::play::{
|
||||
AcknowledgeBlockChange, ClearTitles, CombatDeath, CustomSoundEffect, DisconnectPlay,
|
||||
EntityAnimationS2c, EntityAttributesProperty, EntityEvent, GameEvent, GameStateChangeReason,
|
||||
KeepAliveS2c, LoginPlay, OpenScreen, PlayerPositionLookFlags, PluginMessageS2c, RemoveEntities,
|
||||
ResourcePackS2c, Respawn, SetActionBarText, SetCenterChunk, SetContainerContent,
|
||||
SetDefaultSpawnPosition, SetEntityMetadata, SetEntityVelocity, SetExperience, SetHeadRotation,
|
||||
SetHealth, SetRenderDistance, SetSubtitleText, SetTitleText, SoundCategory,
|
||||
SynchronizePlayerPosition, SystemChatMessage, TeleportEntity, UnloadChunk, UpdateAttributes,
|
||||
UpdateEntityPosition, UpdateEntityPositionAndRotation, UpdateEntityRotation, UpdateTime,
|
||||
};
|
||||
use crate::protocol::packets::{EncodePacket, PacketName};
|
||||
use crate::protocol::{BoundedInt, BoundedString, ByteAngle, RawBytes, SlotId, VarInt};
|
||||
use crate::server::{NewClientData, PlayPacketController, SharedServer};
|
||||
use crate::slab_versioned::{Key, VersionedSlab};
|
||||
use crate::text::Text;
|
||||
use crate::username::Username;
|
||||
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||
use crate::world::{WorldId, Worlds};
|
||||
use crate::{ident, LIBRARY_NAMESPACE};
|
||||
use crate::LIBRARY_NAMESPACE;
|
||||
|
||||
/// Contains the [`ClientEvent`] enum and related data types.
|
||||
mod event;
|
||||
|
@ -222,7 +226,7 @@ pub struct Client<C: Config> {
|
|||
death_location: Option<(DimensionId, BlockPos)>,
|
||||
events: VecDeque<ClientEvent>,
|
||||
/// The ID of the last keepalive sent.
|
||||
last_keepalive_id: i64,
|
||||
last_keepalive_id: u64,
|
||||
/// Entities that were visible to this client at the end of the last tick.
|
||||
/// This is used to determine what entity create/destroy packets should be
|
||||
/// sent.
|
||||
|
@ -232,10 +236,6 @@ pub struct Client<C: Config> {
|
|||
old_game_mode: GameMode,
|
||||
settings: Option<Settings>,
|
||||
block_change_sequence: i32,
|
||||
/// Should be sent after login packet.
|
||||
msgs_to_send: Vec<Text>,
|
||||
bar_to_send: Option<Text>,
|
||||
resource_pack_to_send: Option<ResourcePackS2c>,
|
||||
attack_speed: f64,
|
||||
movement_speed: f64,
|
||||
pub inventory: PlayerInventory, // TODO: make private or pub(crate)
|
||||
|
@ -303,9 +303,6 @@ impl<C: Config> Client<C> {
|
|||
old_game_mode: GameMode::Survival,
|
||||
settings: None,
|
||||
block_change_sequence: 0,
|
||||
msgs_to_send: Vec::new(),
|
||||
bar_to_send: None,
|
||||
resource_pack_to_send: None,
|
||||
attack_speed: 4.0,
|
||||
movement_speed: 0.7,
|
||||
inventory: PlayerInventory::new(),
|
||||
|
@ -325,7 +322,7 @@ impl<C: Config> Client<C> {
|
|||
/// effect if the client is disconnected.
|
||||
pub fn queue_packet<P>(&mut self, pkt: &P)
|
||||
where
|
||||
P: EncodePacket + ?Sized,
|
||||
P: Encode + Packet + ?Sized,
|
||||
{
|
||||
if let Some(ctrl) = &mut self.ctrl {
|
||||
if let Err(e) = ctrl.append_packet(pkt) {
|
||||
|
@ -404,12 +401,13 @@ impl<C: Config> Client<C> {
|
|||
/// Sends a system message to the player which is visible in the chat. The
|
||||
/// message is only visible to this client.
|
||||
pub fn send_message(&mut self, msg: impl Into<Text>) {
|
||||
// We buffer messages because weird things happen if we send them before the
|
||||
// login packet.
|
||||
self.msgs_to_send.push(msg.into());
|
||||
self.queue_packet(&SystemChatMessage {
|
||||
chat: msg.into(),
|
||||
kind: VarInt(0),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_plugin_message(&mut self, channel: Ident<String>, data: Vec<u8>) {
|
||||
pub fn send_plugin_message(&mut self, channel: Ident<&str>, data: &[u8]) {
|
||||
self.queue_packet(&PluginMessageS2c {
|
||||
channel,
|
||||
data: RawBytes(data),
|
||||
|
@ -545,7 +543,7 @@ impl<C: Config> Client<C> {
|
|||
/// Plays a sound to the client at a given position.
|
||||
pub fn play_sound(
|
||||
&mut self,
|
||||
name: Ident<String>,
|
||||
name: Ident<&str>,
|
||||
category: SoundCategory,
|
||||
pos: Vec3<f64>,
|
||||
volume: f32,
|
||||
|
@ -554,7 +552,7 @@ impl<C: Config> Client<C> {
|
|||
self.queue_packet(&CustomSoundEffect {
|
||||
name,
|
||||
category,
|
||||
position: pos.as_() * 8,
|
||||
position: (pos.as_() * 8).into_array(),
|
||||
volume,
|
||||
pitch,
|
||||
seed: 0,
|
||||
|
@ -576,12 +574,10 @@ impl<C: Config> Client<C> {
|
|||
let title = title.into();
|
||||
let subtitle = subtitle.into();
|
||||
|
||||
self.queue_packet(&SetTitleText { text: title });
|
||||
self.queue_packet(&SetTitleText(title));
|
||||
|
||||
if !subtitle.is_empty() {
|
||||
self.queue_packet(&SetSubtitleText {
|
||||
subtitle_text: subtitle,
|
||||
});
|
||||
self.queue_packet(&SetSubtitleText(subtitle));
|
||||
}
|
||||
|
||||
if let Some(anim) = animation.into() {
|
||||
|
@ -591,7 +587,7 @@ impl<C: Config> Client<C> {
|
|||
|
||||
/// Sets the action bar for this client.
|
||||
pub fn set_action_bar(&mut self, text: impl Into<Text>) {
|
||||
self.bar_to_send = Some(text.into());
|
||||
self.queue_packet(&SetActionBarText(text.into()));
|
||||
}
|
||||
|
||||
/// Gets the attack cooldown speed.
|
||||
|
@ -761,14 +757,14 @@ impl<C: Config> Client<C> {
|
|||
/// dialog.
|
||||
pub fn set_resource_pack(
|
||||
&mut self,
|
||||
url: impl Into<String>,
|
||||
hash: impl Into<String>,
|
||||
url: &str,
|
||||
hash: &str,
|
||||
forced: bool,
|
||||
prompt_message: impl Into<Option<Text>>,
|
||||
) {
|
||||
self.resource_pack_to_send = Some(ResourcePackS2c {
|
||||
url: url.into(),
|
||||
hash: BoundedString(hash.into()),
|
||||
self.queue_packet(&ResourcePackS2c {
|
||||
url,
|
||||
hash,
|
||||
forced,
|
||||
prompt_message: prompt_message.into(),
|
||||
});
|
||||
|
@ -915,10 +911,10 @@ impl<C: Config> Client<C> {
|
|||
}
|
||||
C2sPlayPacket::QueryBlockEntityTag(_) => {}
|
||||
C2sPlayPacket::ChangeDifficulty(_) => {}
|
||||
C2sPlayPacket::MessageAcknowledgment(_) => {}
|
||||
C2sPlayPacket::MessageAcknowledgmentC2s(_) => {}
|
||||
C2sPlayPacket::ChatCommand(_) => {}
|
||||
C2sPlayPacket::ChatMessage(p) => self.events.push_back(ClientEvent::ChatMessage {
|
||||
message: p.message.0,
|
||||
message: p.message.into(),
|
||||
timestamp: Duration::from_millis(p.timestamp),
|
||||
}),
|
||||
C2sPlayPacket::ChatPreviewC2s(_) => {}
|
||||
|
@ -930,8 +926,8 @@ impl<C: Config> Client<C> {
|
|||
},
|
||||
C2sPlayPacket::ClientInformation(p) => {
|
||||
self.events.push_back(ClientEvent::SettingsChanged {
|
||||
locale: p.locale.0,
|
||||
view_distance: p.view_distance.0,
|
||||
locale: p.locale.into(),
|
||||
view_distance: p.view_distance,
|
||||
chat_mode: p.chat_mode,
|
||||
chat_colors: p.chat_colors,
|
||||
main_hand: p.main_hand,
|
||||
|
@ -973,13 +969,7 @@ impl<C: Config> Client<C> {
|
|||
self.events.push_back(ClientEvent::InteractWithEntity {
|
||||
id,
|
||||
sneaking: p.sneaking,
|
||||
kind: match p.kind {
|
||||
InteractKind::Interact(hand) => InteractWithEntityKind::Interact(hand),
|
||||
InteractKind::Attack => InteractWithEntityKind::Attack,
|
||||
InteractKind::InteractAt((target, hand)) => {
|
||||
InteractWithEntityKind::InteractAt { target, hand }
|
||||
}
|
||||
},
|
||||
interact: p.interact,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1001,22 +991,22 @@ impl<C: Config> Client<C> {
|
|||
C2sPlayPacket::LockDifficulty(_) => {}
|
||||
C2sPlayPacket::SetPlayerPosition(p) => {
|
||||
if self.pending_teleports == 0 {
|
||||
self.position = p.position;
|
||||
self.position = p.position.into();
|
||||
|
||||
self.events.push_back(ClientEvent::MovePosition {
|
||||
position: p.position,
|
||||
position: p.position.into(),
|
||||
on_ground: p.on_ground,
|
||||
});
|
||||
}
|
||||
}
|
||||
C2sPlayPacket::SetPlayerPositionAndRotation(p) => {
|
||||
if self.pending_teleports == 0 {
|
||||
self.position = p.position;
|
||||
self.position = p.position.into();
|
||||
self.yaw = p.yaw;
|
||||
self.pitch = p.pitch;
|
||||
|
||||
self.events.push_back(ClientEvent::MovePositionAndRotation {
|
||||
position: p.position,
|
||||
position: p.position.into(),
|
||||
yaw: p.yaw,
|
||||
pitch: p.pitch,
|
||||
on_ground: p.on_ground,
|
||||
|
@ -1037,19 +1027,18 @@ impl<C: Config> Client<C> {
|
|||
}
|
||||
C2sPlayPacket::SetPlayerOnGround(p) => {
|
||||
if self.pending_teleports == 0 {
|
||||
self.events.push_back(ClientEvent::MoveOnGround {
|
||||
on_ground: p.on_ground,
|
||||
});
|
||||
self.events
|
||||
.push_back(ClientEvent::MoveOnGround { on_ground: p.0 });
|
||||
}
|
||||
}
|
||||
C2sPlayPacket::MoveVehicleC2s(p) => {
|
||||
if self.pending_teleports == 0 {
|
||||
self.position = p.position;
|
||||
self.position = p.position.into();
|
||||
self.yaw = p.yaw;
|
||||
self.pitch = p.pitch;
|
||||
|
||||
self.events.push_back(ClientEvent::MoveVehicle {
|
||||
position: p.position,
|
||||
position: p.position.into(),
|
||||
yaw: p.yaw,
|
||||
pitch: p.pitch,
|
||||
});
|
||||
|
@ -1070,40 +1059,40 @@ impl<C: Config> Client<C> {
|
|||
}
|
||||
|
||||
self.events.push_back(match p.status {
|
||||
play::DiggingStatus::StartedDigging => ClientEvent::Digging {
|
||||
types::DiggingStatus::StartedDigging => ClientEvent::Digging {
|
||||
status: DiggingStatus::Start,
|
||||
position: p.location,
|
||||
face: p.face,
|
||||
},
|
||||
play::DiggingStatus::CancelledDigging => ClientEvent::Digging {
|
||||
types::DiggingStatus::CancelledDigging => ClientEvent::Digging {
|
||||
status: DiggingStatus::Cancel,
|
||||
position: p.location,
|
||||
face: p.face,
|
||||
},
|
||||
play::DiggingStatus::FinishedDigging => ClientEvent::Digging {
|
||||
types::DiggingStatus::FinishedDigging => ClientEvent::Digging {
|
||||
status: DiggingStatus::Finish,
|
||||
position: p.location,
|
||||
face: p.face,
|
||||
},
|
||||
play::DiggingStatus::DropItemStack => return Ok(()),
|
||||
play::DiggingStatus::DropItem => ClientEvent::DropItem,
|
||||
play::DiggingStatus::ShootArrowOrFinishEating => return Ok(()),
|
||||
play::DiggingStatus::SwapItemInHand => return Ok(()),
|
||||
types::DiggingStatus::DropItemStack => return Ok(()),
|
||||
types::DiggingStatus::DropItem => ClientEvent::DropItem,
|
||||
types::DiggingStatus::ShootArrowOrFinishEating => return Ok(()),
|
||||
types::DiggingStatus::SwapItemInHand => return Ok(()),
|
||||
});
|
||||
}
|
||||
C2sPlayPacket::PlayerCommand(c) => {
|
||||
self.events.push_back(match c.action_id {
|
||||
PlayerCommandId::StartSneaking => ClientEvent::StartSneaking,
|
||||
PlayerCommandId::StopSneaking => ClientEvent::StopSneaking,
|
||||
PlayerCommandId::LeaveBed => ClientEvent::LeaveBed,
|
||||
PlayerCommandId::StartSprinting => ClientEvent::StartSprinting,
|
||||
PlayerCommandId::StopSprinting => ClientEvent::StopSprinting,
|
||||
PlayerCommandId::StartJumpWithHorse => ClientEvent::StartJumpWithHorse {
|
||||
jump_boost: c.jump_boost.0 .0 as u8,
|
||||
Action::StartSneaking => ClientEvent::StartSneaking,
|
||||
Action::StopSneaking => ClientEvent::StopSneaking,
|
||||
Action::LeaveBed => ClientEvent::LeaveBed,
|
||||
Action::StartSprinting => ClientEvent::StartSprinting,
|
||||
Action::StopSprinting => ClientEvent::StopSprinting,
|
||||
Action::StartJumpWithHorse => ClientEvent::StartJumpWithHorse {
|
||||
jump_boost: c.jump_boost.0 as u8,
|
||||
},
|
||||
PlayerCommandId::StopJumpWithHorse => ClientEvent::StopJumpWithHorse,
|
||||
PlayerCommandId::OpenHorseInventory => ClientEvent::OpenHorseInventory,
|
||||
PlayerCommandId::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra,
|
||||
Action::StopJumpWithHorse => ClientEvent::StopJumpWithHorse,
|
||||
Action::OpenHorseInventory => ClientEvent::OpenHorseInventory,
|
||||
Action::StartFlyingWithElytra => ClientEvent::StartFlyingWithElytra,
|
||||
});
|
||||
}
|
||||
C2sPlayPacket::PlayerInput(_) => {}
|
||||
|
@ -1117,9 +1106,9 @@ impl<C: Config> Client<C> {
|
|||
C2sPlayPacket::SeenAdvancements(_) => {}
|
||||
C2sPlayPacket::SelectTrade(_) => {}
|
||||
C2sPlayPacket::SetBeaconEffect(_) => {}
|
||||
C2sPlayPacket::SetHeldItemS2c(e) => {
|
||||
C2sPlayPacket::SetHeldItemC2s(e) => {
|
||||
self.selected_hotbar_slot =
|
||||
PlayerInventory::hotbar_to_slot(e.slot.0).unwrap_or(self.selected_hotbar_slot);
|
||||
PlayerInventory::hotbar_to_slot(e.slot).unwrap_or(self.selected_hotbar_slot);
|
||||
}
|
||||
C2sPlayPacket::ProgramCommandBlock(_) => {}
|
||||
C2sPlayPacket::ProgramCommandBlockMinecart(_) => {}
|
||||
|
@ -1139,14 +1128,14 @@ impl<C: Config> Client<C> {
|
|||
}
|
||||
C2sPlayPacket::PluginMessageC2s(p) => {
|
||||
self.events.push_back(ClientEvent::PluginMessageReceived {
|
||||
channel: p.channel,
|
||||
data: p.data,
|
||||
channel: p.channel.to_owned_ident(),
|
||||
data: p.data.0.to_vec(),
|
||||
});
|
||||
}
|
||||
C2sPlayPacket::ProgramJigsawBlock(_) => {}
|
||||
C2sPlayPacket::ProgramStructureBlock(_) => {}
|
||||
C2sPlayPacket::UpdateSign(_) => {}
|
||||
C2sPlayPacket::SwingArm(p) => self.events.push_back(ClientEvent::ArmSwing(p.hand)),
|
||||
C2sPlayPacket::SwingArm(p) => self.events.push_back(ClientEvent::ArmSwing(p.0)),
|
||||
C2sPlayPacket::TeleportToEntity(_) => {}
|
||||
C2sPlayPacket::UseItemOn(p) => {
|
||||
if p.sequence.0 != 0 {
|
||||
|
@ -1157,7 +1146,7 @@ impl<C: Config> Client<C> {
|
|||
hand: p.hand,
|
||||
location: p.location,
|
||||
face: p.face,
|
||||
cursor_pos: p.cursor_pos,
|
||||
cursor_pos: p.cursor_pos.into(),
|
||||
head_inside_block: p.head_inside_block,
|
||||
sequence: p.sequence,
|
||||
})
|
||||
|
@ -1233,18 +1222,18 @@ impl<C: Config> Client<C> {
|
|||
// The login packet is prepended so that it is sent before all the other
|
||||
// packets. Some packets don't work correctly when sent before the login packet,
|
||||
// which is why we're doing this.
|
||||
ctrl.prepend_packet(&LoginPlay {
|
||||
ctrl.prepend_packet(&LoginPlayOwned {
|
||||
entity_id: 0, // EntityId 0 is reserved for clients.
|
||||
is_hardcore: self.bits.hardcore(),
|
||||
gamemode: self.new_game_mode,
|
||||
previous_gamemode: self.old_game_mode,
|
||||
game_mode: self.new_game_mode,
|
||||
previous_game_mode: -1,
|
||||
dimension_names,
|
||||
registry_codec: shared.registry_codec().clone(),
|
||||
dimension_type_name: world.meta.dimension().dimension_type_name(),
|
||||
dimension_name: world.meta.dimension().dimension_name(),
|
||||
hashed_seed: 10,
|
||||
max_players: VarInt(0),
|
||||
view_distance: BoundedInt(VarInt(self.view_distance() as i32)),
|
||||
view_distance: VarInt(self.view_distance() as i32),
|
||||
simulation_distance: VarInt(16),
|
||||
reduced_debug_info: false,
|
||||
enable_respawn_screen: false,
|
||||
|
@ -1256,7 +1245,7 @@ impl<C: Config> Client<C> {
|
|||
})?;
|
||||
|
||||
if let Some(id) = &self.player_list {
|
||||
player_lists.get(id).initial_packets(ctrl)?;
|
||||
player_lists.get(id).send_initial_packets(ctrl)?;
|
||||
}
|
||||
|
||||
self.teleport(self.position(), self.yaw(), self.pitch());
|
||||
|
@ -1268,24 +1257,24 @@ impl<C: Config> Client<C> {
|
|||
|
||||
// Client bug workaround: send the client to a dummy dimension first.
|
||||
// TODO: is there actually a bug?
|
||||
ctrl.append_packet(&Respawn {
|
||||
ctrl.append_packet(&RespawnOwned {
|
||||
dimension_type_name: DimensionId(0).dimension_type_name(),
|
||||
dimension_name: ident!("{LIBRARY_NAMESPACE}:dummy_dimension"),
|
||||
hashed_seed: 0,
|
||||
game_mode: self.game_mode(),
|
||||
previous_game_mode: self.game_mode(),
|
||||
previous_game_mode: -1,
|
||||
is_debug: false,
|
||||
is_flat: self.bits.flat(),
|
||||
copy_metadata: true,
|
||||
last_death_location: None,
|
||||
})?;
|
||||
|
||||
ctrl.append_packet(&Respawn {
|
||||
ctrl.append_packet(&RespawnOwned {
|
||||
dimension_type_name: world.meta.dimension().dimension_type_name(),
|
||||
dimension_name: world.meta.dimension().dimension_name(),
|
||||
hashed_seed: 0,
|
||||
game_mode: self.game_mode(),
|
||||
previous_game_mode: self.game_mode(),
|
||||
previous_game_mode: -1,
|
||||
is_debug: false,
|
||||
is_flat: self.bits.flat(),
|
||||
copy_metadata: true,
|
||||
|
@ -1310,18 +1299,18 @@ impl<C: Config> Client<C> {
|
|||
if self.old_player_list != self.player_list {
|
||||
// Delete existing entries from old player list.
|
||||
if let Some(id) = &self.old_player_list {
|
||||
player_lists.get(id).clear_packets(ctrl)?;
|
||||
player_lists.get(id).queue_clear_packets(ctrl)?;
|
||||
}
|
||||
|
||||
// Get initial packets for new player list.
|
||||
if let Some(id) = &self.player_list {
|
||||
player_lists.get(id).initial_packets(ctrl)?;
|
||||
player_lists.get(id).send_initial_packets(ctrl)?;
|
||||
}
|
||||
|
||||
self.old_player_list = self.player_list.clone();
|
||||
} else if let Some(id) = &self.player_list {
|
||||
// Update current player list.
|
||||
player_lists.get(id).update_packets(ctrl)?;
|
||||
player_lists.get(id).send_update_packets(ctrl)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1331,8 +1320,8 @@ impl<C: Config> Client<C> {
|
|||
|
||||
ctrl.append_packet(&UpdateAttributes {
|
||||
entity_id: VarInt(0),
|
||||
properties: vec![EntityAttributesProperty {
|
||||
key: ident!("generic.attack_speed"),
|
||||
properties: vec![AttributeProperty {
|
||||
key: Ident::new("generic.attack_speed").unwrap(),
|
||||
value: self.attack_speed,
|
||||
modifiers: Vec::new(),
|
||||
}],
|
||||
|
@ -1344,8 +1333,8 @@ impl<C: Config> Client<C> {
|
|||
|
||||
ctrl.append_packet(&UpdateAttributes {
|
||||
entity_id: VarInt(0),
|
||||
properties: vec![EntityAttributesProperty {
|
||||
key: ident!("generic.movement_speed"),
|
||||
properties: vec![AttributeProperty {
|
||||
key: Ident::new("generic.movement_speed").unwrap(),
|
||||
value: self.movement_speed,
|
||||
modifiers: Vec::new(),
|
||||
}],
|
||||
|
@ -1367,9 +1356,7 @@ impl<C: Config> Client<C> {
|
|||
self.bits.set_view_distance_modified(false);
|
||||
|
||||
if !self.created_this_tick() {
|
||||
ctrl.append_packet(&SetRenderDistance {
|
||||
view_distance: BoundedInt(VarInt(self.view_distance() as i32)),
|
||||
})?;
|
||||
ctrl.append_packet(&SetRenderDistance(VarInt(self.view_distance() as i32)))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1424,10 +1411,15 @@ impl<C: Config> Client<C> {
|
|||
});
|
||||
|
||||
// Load new chunks within the view distance
|
||||
for pos in chunks_in_view_distance(center, self.view_distance) {
|
||||
if let Some(chunk) = world.chunks.get(pos) {
|
||||
if self.loaded_chunks.insert(pos) {
|
||||
ctrl.append_packet(&chunk.chunk_data_packet(pos, shared.biomes().len()))?;
|
||||
{
|
||||
let mut scratch = Vec::new();
|
||||
let biome_registry_len = shared.biomes().len();
|
||||
|
||||
for pos in chunks_in_view_distance(center, self.view_distance) {
|
||||
if let Some(chunk) = world.chunks.get(pos) {
|
||||
if self.loaded_chunks.insert(pos) {
|
||||
chunk.chunk_data_packet(ctrl, &mut scratch, pos, biome_registry_len)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1449,10 +1441,10 @@ impl<C: Config> Client<C> {
|
|||
self.bits.set_teleported_this_tick(false);
|
||||
|
||||
ctrl.append_packet(&SynchronizePlayerPosition {
|
||||
position: self.position,
|
||||
position: self.position.into_array(),
|
||||
yaw: self.yaw,
|
||||
pitch: self.pitch,
|
||||
flags: PlayerPositionLookFlags::new(false, false, false, false, false),
|
||||
flags: SyncPlayerPosLookFlags::new(),
|
||||
teleport_id: VarInt(self.teleport_id_counter as i32),
|
||||
dismount_vehicle: false,
|
||||
})?;
|
||||
|
@ -1473,28 +1465,10 @@ impl<C: Config> Client<C> {
|
|||
|
||||
ctrl.append_packet(&SetEntityVelocity {
|
||||
entity_id: VarInt(0),
|
||||
velocity: velocity_to_packet_units(self.velocity),
|
||||
velocity: velocity_to_packet_units(self.velocity).into_array(),
|
||||
})?;
|
||||
}
|
||||
|
||||
// Send chat messages.
|
||||
for msg in self.msgs_to_send.drain(..) {
|
||||
ctrl.append_packet(&SystemChatMessage {
|
||||
chat: msg,
|
||||
kind: VarInt(0),
|
||||
})?;
|
||||
}
|
||||
|
||||
// Set action bar.
|
||||
if let Some(bar) = self.bar_to_send.take() {
|
||||
ctrl.append_packet(&SetActionBarText { text: bar })?;
|
||||
}
|
||||
|
||||
// Send resource pack prompt.
|
||||
if let Some(p) = self.resource_pack_to_send.take() {
|
||||
ctrl.append_packet(&p)?;
|
||||
}
|
||||
|
||||
let mut entities_to_unload = Vec::new();
|
||||
|
||||
// Update all entities that are visible and unload entities that are no
|
||||
|
@ -1504,9 +1478,7 @@ impl<C: Config> Client<C> {
|
|||
if let Some(entity) = entities.get(id) {
|
||||
debug_assert!(entity.kind() != EntityKind::Marker);
|
||||
if self.position.distance(entity.position()) <= self.view_distance as f64 * 16.0 {
|
||||
if let Some(meta) = entity.updated_tracked_data_packet(id) {
|
||||
let _ = ctrl.append_packet(&meta);
|
||||
}
|
||||
let _ = entity.send_updated_tracked_data(ctrl, id);
|
||||
|
||||
let position_delta = entity.position() - entity.old_position();
|
||||
let needs_teleport = position_delta.map(f64::abs).reduce_partial_max() >= 8.0;
|
||||
|
@ -1518,7 +1490,7 @@ impl<C: Config> Client<C> {
|
|||
{
|
||||
let _ = ctrl.append_packet(&UpdateEntityPositionAndRotation {
|
||||
entity_id: VarInt(id.to_network_id()),
|
||||
delta: (position_delta * 4096.0).as_(),
|
||||
delta: (position_delta * 4096.0).as_::<i16>().into_array(),
|
||||
yaw: ByteAngle::from_degrees(entity.yaw()),
|
||||
pitch: ByteAngle::from_degrees(entity.pitch()),
|
||||
on_ground: entity.on_ground(),
|
||||
|
@ -1527,7 +1499,7 @@ impl<C: Config> Client<C> {
|
|||
if entity.position() != entity.old_position() && !needs_teleport {
|
||||
let _ = ctrl.append_packet(&UpdateEntityPosition {
|
||||
entity_id: VarInt(id.to_network_id()),
|
||||
delta: (position_delta * 4096.0).as_(),
|
||||
delta: (position_delta * 4096.0).as_::<i16>().into_array(),
|
||||
on_ground: entity.on_ground(),
|
||||
});
|
||||
}
|
||||
|
@ -1545,7 +1517,7 @@ impl<C: Config> Client<C> {
|
|||
if needs_teleport {
|
||||
let _ = ctrl.append_packet(&TeleportEntity {
|
||||
entity_id: VarInt(id.to_network_id()),
|
||||
position: entity.position(),
|
||||
position: entity.position().into_array(),
|
||||
yaw: ByteAngle::from_degrees(entity.yaw()),
|
||||
pitch: ByteAngle::from_degrees(entity.pitch()),
|
||||
on_ground: entity.on_ground(),
|
||||
|
@ -1555,7 +1527,7 @@ impl<C: Config> Client<C> {
|
|||
if flags.velocity_modified() {
|
||||
let _ = ctrl.append_packet(&SetEntityVelocity {
|
||||
entity_id: VarInt(id.to_network_id()),
|
||||
velocity: velocity_to_packet_units(entity.velocity()),
|
||||
velocity: velocity_to_packet_units(entity.velocity()).into_array(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1578,7 +1550,7 @@ impl<C: Config> Client<C> {
|
|||
|
||||
if !entities_to_unload.is_empty() {
|
||||
ctrl.append_packet(&RemoveEntities {
|
||||
entities: entities_to_unload,
|
||||
entity_ids: entities_to_unload,
|
||||
})?;
|
||||
}
|
||||
|
||||
|
@ -1591,7 +1563,7 @@ impl<C: Config> Client<C> {
|
|||
|
||||
ctrl.append_packet(&SetEntityMetadata {
|
||||
entity_id: VarInt(0),
|
||||
metadata: RawBytes(data),
|
||||
metadata: RawBytes(&data),
|
||||
})?;
|
||||
}
|
||||
|
||||
|
@ -1620,14 +1592,12 @@ impl<C: Config> Client<C> {
|
|||
&& entity.uuid() != self.uuid
|
||||
&& self.loaded_entities.insert(id)
|
||||
{
|
||||
if let Err(e) = entity.spawn_packets(id, ctrl) {
|
||||
if let Err(e) = entity.send_spawn_packets(id, ctrl) {
|
||||
return Some(e);
|
||||
}
|
||||
|
||||
if let Some(meta) = entity.initial_tracked_data_packet(id) {
|
||||
if let Err(e) = ctrl.append_packet(&meta) {
|
||||
return Some(e);
|
||||
}
|
||||
if let Err(e) = entity.send_initial_tracked_data(ctrl, id) {
|
||||
return Some(e);
|
||||
}
|
||||
|
||||
if let Err(e) = send_entity_events(ctrl, id.to_network_id(), entity.events()) {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use valence_protocol::block::BlockFace;
|
||||
use valence_protocol::block_pos::BlockPos;
|
||||
use valence_protocol::entity_meta::Pose;
|
||||
use valence_protocol::ident::Ident;
|
||||
use valence_protocol::item::ItemStack;
|
||||
use valence_protocol::packets::c2s::play::ResourcePackC2s;
|
||||
use valence_protocol::types::{
|
||||
ChatMode, ClickContainerMode, DisplayedSkinParts, EntityInteraction, Hand, MainHand,
|
||||
};
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use vek::Vec3;
|
||||
|
||||
use super::Client;
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::config::Config;
|
||||
use crate::entity::types::Pose;
|
||||
use crate::entity::{Entity, EntityEvent, EntityId, TrackedData};
|
||||
use crate::ident::Ident;
|
||||
use crate::inventory::{Inventory, InventoryDirtyable};
|
||||
use crate::item::ItemStack;
|
||||
use crate::protocol::packets::c2s::play::ClickContainerMode;
|
||||
pub use crate::protocol::packets::c2s::play::{
|
||||
BlockFace, ChatMode, DisplayedSkinParts, Hand, MainHand, ResourcePackC2s as ResourcePackStatus,
|
||||
};
|
||||
pub use crate::protocol::packets::s2c::play::GameMode;
|
||||
use crate::protocol::{RawBytes, Slot, SlotId, VarInt};
|
||||
use crate::inventory::{Inventory, InventoryDirtyable, SlotId};
|
||||
|
||||
/// Represents an action performed by a client.
|
||||
///
|
||||
|
@ -104,7 +104,7 @@ pub enum ClientEvent {
|
|||
/// If the client was sneaking during the interaction.
|
||||
sneaking: bool,
|
||||
/// The kind of interaction that occurred.
|
||||
kind: InteractWithEntityKind,
|
||||
interact: EntityInteraction,
|
||||
},
|
||||
SteerBoat {
|
||||
left_paddle_turning: bool,
|
||||
|
@ -134,9 +134,9 @@ pub enum ClientEvent {
|
|||
},
|
||||
PluginMessageReceived {
|
||||
channel: Ident<String>,
|
||||
data: RawBytes,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
ResourcePackStatusChanged(ResourcePackStatus),
|
||||
ResourcePackStatusChanged(ResourcePackC2s),
|
||||
/// The client closed a screen. This occurs when the client closes their
|
||||
/// inventory, closes a chest inventory, etc.
|
||||
CloseScreen {
|
||||
|
@ -160,7 +160,7 @@ pub enum ClientEvent {
|
|||
/// The slot number that the client is trying to set.
|
||||
slot_id: SlotId,
|
||||
/// The contents of the slot.
|
||||
slot: Slot,
|
||||
slot: Option<ItemStack>,
|
||||
},
|
||||
/// The client is in survival mode, and is trying to modify an inventory.
|
||||
ClickContainer {
|
||||
|
@ -174,9 +174,9 @@ pub enum ClientEvent {
|
|||
///
|
||||
/// It's not safe to blindly trust the contents of this. Servers need to
|
||||
/// validate it if they want to prevent item duping.
|
||||
slot_changes: Vec<(SlotId, Slot)>,
|
||||
slot_changes: Vec<(SlotId, Option<ItemStack>)>,
|
||||
/// The item that is now being carried by the user's cursor
|
||||
carried_item: Slot,
|
||||
carried_item: Option<ItemStack>,
|
||||
},
|
||||
RespawnRequest,
|
||||
}
|
||||
|
@ -197,13 +197,6 @@ pub struct Settings {
|
|||
pub allow_server_listings: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum InteractWithEntityKind {
|
||||
Interact(Hand),
|
||||
InteractAt { target: Vec3<f32>, hand: Hand },
|
||||
Attack,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum DiggingStatus {
|
||||
/// The client started digging a block.
|
||||
|
|
|
@ -7,13 +7,13 @@ use async_trait::async_trait;
|
|||
use serde::Serialize;
|
||||
use tokio::runtime::Handle as TokioHandle;
|
||||
use uuid::Uuid;
|
||||
use valence_protocol::text::Text;
|
||||
use valence_protocol::username::Username;
|
||||
use valence_protocol::MAX_PACKET_SIZE;
|
||||
|
||||
use crate::biome::Biome;
|
||||
use crate::dimension::Dimension;
|
||||
use crate::protocol::MAX_PACKET_SIZE;
|
||||
use crate::server::{NewClientData, Server, SharedServer};
|
||||
use crate::text::Text;
|
||||
use crate::username::Username;
|
||||
use crate::{Ticks, STANDARD_TPS};
|
||||
|
||||
/// A trait for the configuration of a server.
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
use anyhow::ensure;
|
||||
use valence_nbt::{compound, Compound};
|
||||
use valence_protocol::ident;
|
||||
use valence_protocol::ident::Ident;
|
||||
|
||||
use crate::ident::Ident;
|
||||
use crate::{ident, LIBRARY_NAMESPACE};
|
||||
use crate::LIBRARY_NAMESPACE;
|
||||
|
||||
/// Identifies a particular [`Dimension`] on the server.
|
||||
///
|
||||
|
|
|
@ -9,14 +9,16 @@ use bitfield_struct::bitfield;
|
|||
pub use data::{EntityKind, TrackedData};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use uuid::Uuid;
|
||||
use valence_protocol::byte_angle::ByteAngle;
|
||||
use valence_protocol::entity_meta::{Facing, PaintingKind, Pose};
|
||||
use valence_protocol::packets::s2c::play::{
|
||||
SetEntityMetadata, SetHeadRotation, SpawnEntity, SpawnExperienceOrb, SpawnPlayer,
|
||||
};
|
||||
use valence_protocol::raw_bytes::RawBytes;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use vek::{Aabb, Vec3};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::entity::types::{Facing, PaintingKind, Pose};
|
||||
use crate::protocol::packets::s2c::play::{
|
||||
SetEntityMetadata, SetHeadRotation, SpawnEntity, SpawnExperienceOrb, SpawnPlayer,
|
||||
};
|
||||
use crate::protocol::{ByteAngle, RawBytes, VarInt};
|
||||
use crate::server::PlayPacketController;
|
||||
use crate::slab_versioned::{Key, VersionedSlab};
|
||||
use crate::util::aabb_from_bottom_and_size;
|
||||
|
@ -24,7 +26,6 @@ use crate::world::WorldId;
|
|||
use crate::STANDARD_TPS;
|
||||
|
||||
pub mod data;
|
||||
pub mod types;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/entity_event.rs"));
|
||||
|
||||
|
@ -240,7 +241,7 @@ impl EntityId {
|
|||
/// The value of the default entity ID which is always invalid.
|
||||
pub const NULL: Self = Self(Key::NULL);
|
||||
|
||||
pub(crate) fn to_network_id(self) -> i32 {
|
||||
pub fn to_network_id(self) -> i32 {
|
||||
self.0.version().get() as i32
|
||||
}
|
||||
}
|
||||
|
@ -706,40 +707,44 @@ impl<C: Config> Entity<C> {
|
|||
aabb_from_bottom_and_size(self.new_position, dimensions.into())
|
||||
}
|
||||
|
||||
/// Gets the tracked data packet to send to clients after this entity has
|
||||
/// Queues the tracked data packet to send to clients after this entity has
|
||||
/// been spawned.
|
||||
///
|
||||
/// Returns `None` if all the tracked data is at its default values.
|
||||
pub(crate) fn initial_tracked_data_packet(
|
||||
pub(crate) fn send_initial_tracked_data(
|
||||
&self,
|
||||
ctrl: &mut PlayPacketController,
|
||||
this_id: EntityId,
|
||||
) -> Option<SetEntityMetadata> {
|
||||
self.variants
|
||||
.initial_tracked_data()
|
||||
.map(|meta| SetEntityMetadata {
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: cache metadata buffer?
|
||||
if let Some(metadata) = self.variants.initial_tracked_data() {
|
||||
ctrl.append_packet(&SetEntityMetadata {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
metadata: RawBytes(meta),
|
||||
})
|
||||
metadata: RawBytes(&metadata),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the tracked data packet to send to clients when the entity is
|
||||
/// Queues the tracked data packet to send to clients when the entity is
|
||||
/// modified.
|
||||
///
|
||||
/// Returns `None` if this entity's tracked data has not been modified.
|
||||
pub(crate) fn updated_tracked_data_packet(
|
||||
pub(crate) fn send_updated_tracked_data(
|
||||
&self,
|
||||
ctrl: &mut PlayPacketController,
|
||||
this_id: EntityId,
|
||||
) -> Option<SetEntityMetadata> {
|
||||
self.variants
|
||||
.updated_tracked_data()
|
||||
.map(|meta| SetEntityMetadata {
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: cache metadata buffer?
|
||||
if let Some(metadata) = self.variants.updated_tracked_data() {
|
||||
ctrl.append_packet(&SetEntityMetadata {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
metadata: RawBytes(meta),
|
||||
})
|
||||
metadata: RawBytes(&metadata),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends the appropriate packets to spawn the entity.
|
||||
pub(crate) fn spawn_packets(
|
||||
pub(crate) fn send_spawn_packets(
|
||||
&self,
|
||||
this_id: EntityId,
|
||||
ctrl: &mut PlayPacketController,
|
||||
|
@ -748,26 +753,26 @@ impl<C: Config> Entity<C> {
|
|||
entity_id: VarInt(this_id.to_network_id()),
|
||||
object_uuid: self.uuid,
|
||||
kind: VarInt(self.kind() as i32),
|
||||
position: self.new_position,
|
||||
position: self.new_position.into_array(),
|
||||
pitch: ByteAngle::from_degrees(self.pitch),
|
||||
yaw: ByteAngle::from_degrees(self.yaw),
|
||||
head_yaw: ByteAngle::from_degrees(self.head_yaw),
|
||||
data: VarInt(data),
|
||||
velocity: velocity_to_packet_units(self.velocity),
|
||||
velocity: velocity_to_packet_units(self.velocity).into_array(),
|
||||
};
|
||||
|
||||
match &self.variants {
|
||||
TrackedData::Marker(_) => {}
|
||||
TrackedData::ExperienceOrb(_) => ctrl.append_packet(&SpawnExperienceOrb {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
position: self.new_position,
|
||||
position: self.new_position.into_array(),
|
||||
count: 0, // TODO
|
||||
})?,
|
||||
TrackedData::Player(_) => {
|
||||
ctrl.append_packet(&SpawnPlayer {
|
||||
entity_id: VarInt(this_id.to_network_id()),
|
||||
player_uuid: self.uuid,
|
||||
position: self.new_position,
|
||||
position: self.new_position.into_array(),
|
||||
yaw: ByteAngle::from_degrees(self.yaw),
|
||||
pitch: ByteAngle::from_degrees(self.pitch),
|
||||
})?;
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
#![allow(clippy::all, missing_docs, trivial_numeric_casts)]
|
||||
|
||||
use crate::block::{BlockPos, BlockState};
|
||||
use crate::entity::types::*;
|
||||
use crate::protocol::{Encode, VarInt};
|
||||
use crate::text::Text;
|
||||
use crate::uuid::Uuid;
|
||||
use uuid::Uuid;
|
||||
use valence_protocol::block::BlockState;
|
||||
use valence_protocol::block_pos::BlockPos;
|
||||
use valence_protocol::entity_meta::*;
|
||||
use valence_protocol::text::Text;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use valence_protocol::Encode;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/entity.rs"));
|
||||
|
|
|
@ -1,331 +0,0 @@
|
|||
//! Primitive types used in getters and setters on entities.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use crate::protocol::{Decode, Encode, VarInt};
|
||||
|
||||
/// Represents an optional `u32` value excluding [`u32::MAX`].
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct OptionalInt(u32);
|
||||
|
||||
impl OptionalInt {
|
||||
/// Returns `None` iff `n` is Some(u32::MAX).
|
||||
pub fn new(n: impl Into<Option<u32>>) -> Option<Self> {
|
||||
match n.into() {
|
||||
None => Some(Self(0)),
|
||||
Some(u32::MAX) => None,
|
||||
Some(n) => Some(Self(n + 1)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self) -> Option<u32> {
|
||||
self.0.checked_sub(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for OptionalInt {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(self.0 as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(self.0 as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for OptionalInt {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Self(VarInt::decode(r)?.0 as u32))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
|
||||
pub struct EulerAngle {
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
pub roll: f32,
|
||||
}
|
||||
|
||||
impl EulerAngle {
|
||||
pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self {
|
||||
Self { pitch, yaw, roll }
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for EulerAngle {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.pitch.encode(w)?;
|
||||
self.yaw.encode(w)?;
|
||||
self.roll.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.pitch.encoded_len() + self.yaw.encoded_len() + self.roll.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum Facing {
|
||||
Down,
|
||||
Up,
|
||||
North,
|
||||
South,
|
||||
West,
|
||||
East,
|
||||
}
|
||||
|
||||
impl Encode for Facing {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct VillagerData {
|
||||
pub kind: VillagerKind,
|
||||
pub profession: VillagerProfession,
|
||||
pub level: i32,
|
||||
}
|
||||
|
||||
impl VillagerData {
|
||||
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
profession,
|
||||
level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VillagerData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kind: Default::default(),
|
||||
profession: Default::default(),
|
||||
level: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for VillagerData {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(self.kind as i32).encode(w)?;
|
||||
VarInt(self.profession as i32).encode(w)?;
|
||||
VarInt(self.level).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(self.kind as i32).encoded_len()
|
||||
+ VarInt(self.profession as i32).encoded_len()
|
||||
+ VarInt(self.level).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum VillagerKind {
|
||||
Desert,
|
||||
Jungle,
|
||||
#[default]
|
||||
Plains,
|
||||
Savanna,
|
||||
Snow,
|
||||
Swamp,
|
||||
Taiga,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum VillagerProfession {
|
||||
#[default]
|
||||
None,
|
||||
Armorer,
|
||||
Butcher,
|
||||
Cartographer,
|
||||
Cleric,
|
||||
Farmer,
|
||||
Fisherman,
|
||||
Fletcher,
|
||||
Leatherworker,
|
||||
Librarian,
|
||||
Mason,
|
||||
Nitwit,
|
||||
Shepherd,
|
||||
Toolsmith,
|
||||
Weaponsmith,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum Pose {
|
||||
#[default]
|
||||
Standing,
|
||||
FallFlying,
|
||||
Sleeping,
|
||||
Swimming,
|
||||
SpinAttack,
|
||||
Sneaking,
|
||||
LongJumping,
|
||||
Dying,
|
||||
Croaking,
|
||||
UsingTongue,
|
||||
Roaring,
|
||||
Sniffing,
|
||||
Emerging,
|
||||
Digging,
|
||||
}
|
||||
|
||||
impl Encode for Pose {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
/// The main hand of a player.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum MainArm {
|
||||
Left,
|
||||
#[default]
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Encode for MainArm {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
(*self as u8).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum BoatKind {
|
||||
#[default]
|
||||
Oak,
|
||||
Spruce,
|
||||
Birch,
|
||||
Jungle,
|
||||
Acacia,
|
||||
DarkOak,
|
||||
}
|
||||
|
||||
impl Encode for BoatKind {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum CatKind {
|
||||
Tabby,
|
||||
#[default]
|
||||
Black,
|
||||
Red,
|
||||
Siamese,
|
||||
BritishShorthair,
|
||||
Calico,
|
||||
Persian,
|
||||
Ragdoll,
|
||||
White,
|
||||
Jellie,
|
||||
AllBlack,
|
||||
}
|
||||
|
||||
impl Encode for CatKind {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum FrogKind {
|
||||
#[default]
|
||||
Temperate,
|
||||
Warm,
|
||||
Cold,
|
||||
}
|
||||
|
||||
impl Encode for FrogKind {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub enum PaintingKind {
|
||||
#[default]
|
||||
Kebab,
|
||||
Aztec,
|
||||
Alban,
|
||||
Aztec2,
|
||||
Bomb,
|
||||
Plant,
|
||||
Wasteland,
|
||||
Pool,
|
||||
Courbet,
|
||||
Sea,
|
||||
Sunset,
|
||||
Creebet,
|
||||
Wanderer,
|
||||
Graham,
|
||||
Match,
|
||||
Bust,
|
||||
Stage,
|
||||
Void,
|
||||
SkullAndRoses,
|
||||
Wither,
|
||||
Fighters,
|
||||
Pointer,
|
||||
Pigscene,
|
||||
BurningSkull,
|
||||
Skeleton,
|
||||
Earth,
|
||||
Wind,
|
||||
Water,
|
||||
Fire,
|
||||
DonkeyKong,
|
||||
}
|
||||
|
||||
impl Encode for PaintingKind {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum Particle {
|
||||
EntityEffect = 21,
|
||||
}
|
||||
|
||||
impl Encode for Particle {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt(*self as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(*self as i32).encoded_len()
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use thiserror::Error;
|
||||
use valence_protocol::item::ItemStack;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
|
||||
use crate::item::ItemStack;
|
||||
use crate::protocol::{SlotId, VarInt};
|
||||
use crate::slab_versioned::{Key, VersionedSlab};
|
||||
|
||||
pub type SlotId = i16;
|
||||
|
||||
pub trait Inventory {
|
||||
fn slot(&self, slot_id: SlotId) -> Option<&ItemStack>;
|
||||
/// Sets the slot to the desired contents. Returns the previous contents of
|
||||
|
@ -269,14 +271,15 @@ impl Inventories {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Copy, Clone, Debug, Error)]
|
||||
#[error("InventoryError")]
|
||||
pub struct InventoryError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use valence_protocol::item::{ItemKind, ItemStack};
|
||||
|
||||
use super::*;
|
||||
use crate::item::{ItemKind, ItemStack};
|
||||
|
||||
#[test]
|
||||
fn test_get_set_slots() {
|
||||
|
|
91
src/item.rs
91
src/item.rs
|
@ -1,91 +0,0 @@
|
|||
//! Items and ItemStacks
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::block::BlockKind;
|
||||
use crate::nbt::Compound;
|
||||
use crate::protocol::{Decode, Encode, VarInt};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/item.rs"));
|
||||
|
||||
impl Encode for ItemKind {
|
||||
fn encode(&self, w: &mut impl std::io::Write) -> anyhow::Result<()> {
|
||||
VarInt(self.to_raw() as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(self.to_raw() as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for ItemKind {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let id = VarInt::decode(r)?.0;
|
||||
let errmsg = "invalid item ID";
|
||||
|
||||
ItemKind::from_raw(id.try_into().context(errmsg)?).context(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ItemStack {
|
||||
pub item: ItemKind,
|
||||
item_count: u8,
|
||||
pub nbt: Option<Compound>,
|
||||
}
|
||||
|
||||
impl ItemStack {
|
||||
const STACK_MIN: u8 = 1;
|
||||
const STACK_MAX: u8 = 127;
|
||||
|
||||
pub fn new(item: ItemKind, count: u8, nbt: Option<Compound>) -> Self {
|
||||
Self {
|
||||
item,
|
||||
item_count: count.clamp(Self::STACK_MIN, Self::STACK_MAX),
|
||||
nbt,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the number of items in this stack.
|
||||
pub fn count(&self) -> u8 {
|
||||
self.item_count
|
||||
}
|
||||
|
||||
/// Sets the number of items in this stack. Values are clamped to 1-127,
|
||||
/// which are the positive values accepted by clients.
|
||||
pub fn set_count(&mut self, count: u8) {
|
||||
self.item_count = count.clamp(Self::STACK_MIN, Self::STACK_MAX)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn item_kind_to_block_kind() {
|
||||
assert_eq!(
|
||||
ItemKind::Cauldron.to_block_kind(),
|
||||
Some(BlockKind::Cauldron)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_state_to_item() {
|
||||
assert_eq!(BlockKind::Torch.to_item_kind(), ItemKind::Torch);
|
||||
assert_eq!(BlockKind::WallTorch.to_item_kind(), ItemKind::Torch);
|
||||
|
||||
assert_eq!(BlockKind::Cauldron.to_item_kind(), ItemKind::Cauldron);
|
||||
assert_eq!(BlockKind::LavaCauldron.to_item_kind(), ItemKind::Cauldron);
|
||||
|
||||
assert_eq!(BlockKind::NetherPortal.to_item_kind(), ItemKind::Air);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_stack_clamps_count() {
|
||||
let mut stack = ItemStack::new(ItemKind::Stone, 200, None);
|
||||
assert_eq!(stack.item_count, ItemStack::STACK_MAX);
|
||||
stack.set_count(201);
|
||||
assert_eq!(stack.item_count, ItemStack::STACK_MAX);
|
||||
}
|
||||
}
|
46
src/lib.rs
46
src/lib.rs
|
@ -96,34 +96,27 @@
|
|||
pub use async_trait::async_trait;
|
||||
#[doc(inline)]
|
||||
pub use server::start_server;
|
||||
pub use valence_protocol as protocol;
|
||||
#[doc(inline)]
|
||||
pub use {uuid, valence_nbt as nbt, vek};
|
||||
|
||||
pub mod biome;
|
||||
pub mod block;
|
||||
mod block_pos;
|
||||
mod bvh;
|
||||
pub mod chunk;
|
||||
mod chunk_pos;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod dimension;
|
||||
pub mod enchant;
|
||||
pub mod entity;
|
||||
pub mod ident;
|
||||
pub mod inventory;
|
||||
pub mod item;
|
||||
pub mod player_list;
|
||||
pub mod player_textures;
|
||||
#[doc(hidden)]
|
||||
pub mod protocol;
|
||||
pub mod server;
|
||||
mod slab;
|
||||
mod slab_rc;
|
||||
mod slab_versioned;
|
||||
pub mod spatial_index;
|
||||
pub mod text;
|
||||
pub mod username;
|
||||
pub mod util;
|
||||
pub mod world;
|
||||
|
||||
|
@ -131,49 +124,42 @@ pub mod world;
|
|||
/// library.
|
||||
pub mod prelude {
|
||||
pub use biome::{Biome, BiomeId};
|
||||
pub use block::{BlockKind, BlockPos, BlockState, PropName, PropValue};
|
||||
pub use chunk::{Chunk, ChunkPos, Chunks, LoadedChunk, UnloadedChunk};
|
||||
pub use client::{handle_event_default, Client, ClientEvent, ClientId, Clients, GameMode};
|
||||
pub use client::{handle_event_default, Client, ClientEvent, ClientId, Clients};
|
||||
pub use config::{Config, ConnectionMode, PlayerSampleEntry, ServerListPing};
|
||||
pub use dimension::{Dimension, DimensionId};
|
||||
pub use entity::{Entities, Entity, EntityEvent, EntityId, EntityKind, TrackedData};
|
||||
pub use ident::{Ident, IdentError};
|
||||
pub use inventory::{
|
||||
ConfigurableInventory, Inventories, Inventory, InventoryId, PlayerInventory,
|
||||
ConfigurableInventory, Inventories, Inventory, InventoryId, PlayerInventory, SlotId,
|
||||
};
|
||||
pub use item::{ItemKind, ItemStack};
|
||||
pub use player_list::{PlayerList, PlayerListEntry, PlayerListId, PlayerLists};
|
||||
pub use server::{NewClientData, Server, SharedServer, ShutdownResult};
|
||||
pub use spatial_index::{RaycastHit, SpatialIndex};
|
||||
pub use text::{Color, Text, TextFormat};
|
||||
pub use username::Username;
|
||||
pub use util::{
|
||||
chunks_in_view_distance, from_yaw_and_pitch, is_chunk_in_view_distance, to_yaw_and_pitch,
|
||||
};
|
||||
pub use uuid::Uuid;
|
||||
pub use valence_nbt::Compound;
|
||||
pub use valence_protocol::block::{BlockKind, BlockState, PropName, PropValue};
|
||||
pub use valence_protocol::block_pos::BlockPos;
|
||||
pub use valence_protocol::entity_meta::Pose;
|
||||
pub use valence_protocol::ident::{Ident, IdentError};
|
||||
pub use valence_protocol::item::{ItemKind, ItemStack};
|
||||
pub use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
|
||||
pub use valence_protocol::text::{Color, Text, TextFormat};
|
||||
pub use valence_protocol::types::{GameMode, Hand, SoundCategory};
|
||||
pub use valence_protocol::username::Username;
|
||||
pub use valence_protocol::{ident, MINECRAFT_VERSION, PROTOCOL_VERSION};
|
||||
pub use vek::{Aabb, Mat2, Mat3, Mat4, Vec2, Vec3, Vec4};
|
||||
pub use world::{World, WorldId, WorldMeta, Worlds};
|
||||
|
||||
use super::*;
|
||||
pub use crate::{
|
||||
async_trait, ident, nbt, vek, Ticks, LIBRARY_NAMESPACE, PROTOCOL_VERSION, STANDARD_TPS,
|
||||
VERSION_NAME,
|
||||
};
|
||||
pub use crate::{async_trait, nbt, vek, Ticks, STANDARD_TPS};
|
||||
}
|
||||
|
||||
/// The Minecraft protocol version this library currently targets.
|
||||
pub const PROTOCOL_VERSION: i32 = 760;
|
||||
|
||||
/// The name of the Minecraft version this library currently targets, e.g.
|
||||
/// "1.8.2"
|
||||
pub const VERSION_NAME: &str = "1.19.2";
|
||||
|
||||
/// The namespace for this library used internally for
|
||||
/// [identifiers](crate::ident::Ident).
|
||||
///
|
||||
/// You should avoid using this namespace in your own identifiers.
|
||||
pub const LIBRARY_NAMESPACE: &str = "valence";
|
||||
/// [identifiers](valence_protocol::ident::Ident).
|
||||
const LIBRARY_NAMESPACE: &str = "valence";
|
||||
|
||||
/// The most recent version of the [Velocity] proxy which has been tested to
|
||||
/// work with Valence. The elements of the tuple are (major, minor, patch)
|
||||
|
|
|
@ -5,18 +5,15 @@ use std::collections::{HashMap, HashSet};
|
|||
|
||||
use bitfield_struct::bitfield;
|
||||
use uuid::Uuid;
|
||||
use valence_protocol::packets::s2c::play::{PlayerInfo, SetTabListHeaderAndFooter};
|
||||
use valence_protocol::text::Text;
|
||||
use valence_protocol::types::{GameMode, PlayerInfoAddPlayer, SignedProperty};
|
||||
use valence_protocol::var_int::VarInt;
|
||||
|
||||
use crate::client::GameMode;
|
||||
use crate::config::Config;
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::packets::s2c::play::{
|
||||
PlayerInfo, PlayerListAddPlayer, SetTabListHeaderAndFooter,
|
||||
};
|
||||
use crate::protocol::packets::Property;
|
||||
use crate::protocol::VarInt;
|
||||
use crate::server::PlayPacketController;
|
||||
use crate::slab_rc::{Key, SlabRc};
|
||||
use crate::text::Text;
|
||||
|
||||
/// A container for all [`PlayerList`]s on a server.
|
||||
pub struct PlayerLists<C: Config> {
|
||||
|
@ -126,7 +123,7 @@ impl<C: Config> PlayerList<C> {
|
|||
textures: Option<SignedPlayerTextures>,
|
||||
game_mode: GameMode,
|
||||
ping: i32,
|
||||
display_name: impl Into<Option<Text>>,
|
||||
display_name: Option<Text>,
|
||||
) -> bool {
|
||||
match self.entries.entry(uuid) {
|
||||
Entry::Occupied(mut oe) => {
|
||||
|
@ -141,7 +138,7 @@ impl<C: Config> PlayerList<C> {
|
|||
textures,
|
||||
game_mode,
|
||||
ping,
|
||||
display_name: display_name.into(),
|
||||
display_name,
|
||||
bits: EntryBits::new().with_created_this_tick(true),
|
||||
});
|
||||
} else {
|
||||
|
@ -157,7 +154,7 @@ impl<C: Config> PlayerList<C> {
|
|||
textures,
|
||||
game_mode,
|
||||
ping,
|
||||
display_name: display_name.into(),
|
||||
display_name,
|
||||
bits: EntryBits::new().with_created_this_tick(true),
|
||||
});
|
||||
true
|
||||
|
@ -247,20 +244,23 @@ impl<C: Config> PlayerList<C> {
|
|||
self.entries.iter_mut().map(|(k, v)| (*k, v))
|
||||
}
|
||||
|
||||
pub(crate) fn initial_packets(&self, ctrl: &mut PlayPacketController) -> anyhow::Result<()> {
|
||||
pub(crate) fn send_initial_packets(
|
||||
&self,
|
||||
ctrl: &mut PlayPacketController,
|
||||
) -> anyhow::Result<()> {
|
||||
let add_player: Vec<_> = self
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(&uuid, e)| PlayerListAddPlayer {
|
||||
.map(|(&uuid, e)| PlayerInfoAddPlayer {
|
||||
uuid,
|
||||
username: e.username.clone().into(),
|
||||
username: &e.username,
|
||||
properties: {
|
||||
let mut properties = Vec::new();
|
||||
if let Some(textures) = &e.textures {
|
||||
properties.push(Property {
|
||||
name: "textures".into(),
|
||||
value: base64::encode(textures.payload()),
|
||||
signature: Some(base64::encode(textures.signature())),
|
||||
properties.push(SignedProperty {
|
||||
name: "textures",
|
||||
value: textures.payload(),
|
||||
signature: Some(textures.signature()),
|
||||
});
|
||||
}
|
||||
properties
|
||||
|
@ -286,7 +286,10 @@ impl<C: Config> PlayerList<C> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn update_packets(&self, ctrl: &mut PlayPacketController) -> anyhow::Result<()> {
|
||||
pub(crate) fn send_update_packets(
|
||||
&self,
|
||||
ctrl: &mut PlayPacketController,
|
||||
) -> anyhow::Result<()> {
|
||||
if !self.removed.is_empty() {
|
||||
ctrl.append_packet(&PlayerInfo::RemovePlayer(
|
||||
self.removed.iter().cloned().collect(),
|
||||
|
@ -302,16 +305,16 @@ impl<C: Config> PlayerList<C> {
|
|||
if e.bits.created_this_tick() {
|
||||
let mut properties = Vec::new();
|
||||
if let Some(textures) = &e.textures {
|
||||
properties.push(Property {
|
||||
name: "textures".into(),
|
||||
value: base64::encode(textures.payload()),
|
||||
signature: Some(base64::encode(textures.signature())),
|
||||
properties.push(SignedProperty {
|
||||
name: "textures",
|
||||
value: textures.payload(),
|
||||
signature: Some(textures.signature()),
|
||||
});
|
||||
}
|
||||
|
||||
add_player.push(PlayerListAddPlayer {
|
||||
add_player.push(PlayerInfoAddPlayer {
|
||||
uuid,
|
||||
username: e.username.clone().into(),
|
||||
username: e.username(),
|
||||
properties,
|
||||
game_mode: e.game_mode,
|
||||
ping: VarInt(e.ping),
|
||||
|
@ -361,7 +364,10 @@ impl<C: Config> PlayerList<C> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn clear_packets(&self, ctrl: &mut PlayPacketController) -> anyhow::Result<()> {
|
||||
pub(crate) fn queue_clear_packets(
|
||||
&self,
|
||||
ctrl: &mut PlayPacketController,
|
||||
) -> anyhow::Result<()> {
|
||||
ctrl.append_packet(&PlayerInfo::RemovePlayer(
|
||||
self.entries.keys().cloned().collect(),
|
||||
))
|
||||
|
|
|
@ -9,19 +9,27 @@ use url::Url;
|
|||
/// by the server.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct SignedPlayerTextures {
|
||||
payload: Box<[u8]>,
|
||||
signature: Box<[u8]>,
|
||||
payload: Box<str>,
|
||||
signature: Box<str>,
|
||||
skin_url: Box<str>,
|
||||
cape_url: Option<Box<str>>,
|
||||
}
|
||||
|
||||
impl SignedPlayerTextures {
|
||||
/// Constructs the signed player textures from payload and signature
|
||||
/// components in base64.
|
||||
///
|
||||
/// Note that this does not validate that the signature is valid for the
|
||||
/// given payload.
|
||||
pub(crate) fn from_base64(
|
||||
payload: impl AsRef<str>,
|
||||
signature: impl AsRef<str>,
|
||||
payload: impl Into<Box<str>>,
|
||||
signature: impl Into<Box<str>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let payload = base64::decode(payload.as_ref())?;
|
||||
let signature = base64::decode(signature.as_ref())?;
|
||||
let payload = payload.into();
|
||||
let signature = signature.into();
|
||||
|
||||
let payload_decoded = base64::decode(payload.as_bytes())?;
|
||||
base64::decode(signature.as_bytes())?;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Textures {
|
||||
|
@ -41,21 +49,23 @@ impl SignedPlayerTextures {
|
|||
url: Url,
|
||||
}
|
||||
|
||||
let textures: Textures = serde_json::from_slice(&payload)?;
|
||||
let textures: Textures = serde_json::from_slice(&payload_decoded)?;
|
||||
|
||||
Ok(Self {
|
||||
payload: payload.into(),
|
||||
signature: signature.into(),
|
||||
payload,
|
||||
signature,
|
||||
skin_url: String::from(textures.textures.skin.url).into(),
|
||||
cape_url: textures.textures.cape.map(|t| String::from(t.url).into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn payload(&self) -> &[u8] {
|
||||
/// The payload in base64.
|
||||
pub(crate) fn payload(&self) -> &str {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
pub(crate) fn signature(&self) -> &[u8] {
|
||||
/// The signature in base64.
|
||||
pub(crate) fn signature(&self) -> &str {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
|
|
873
src/protocol.rs
873
src/protocol.rs
|
@ -1,873 +0,0 @@
|
|||
//! Provides low-level access to the Minecraft protocol.
|
||||
//!
|
||||
//! Contained within are the definitions of Minecraft's [`packets`] and the
|
||||
//! [`codec`] module for performing packet IO.
|
||||
//!
|
||||
//! While the protocol module is technically public API, its use is discouraged
|
||||
//! and has thus been hidden from the documentation. You may find yourself
|
||||
//! needing to use this module under the following circumstances:
|
||||
//! - You want to send packets to clients manually using the [`queue_packet`]
|
||||
//! function.
|
||||
//! - You are writing a proxy between the client and server.
|
||||
//! - You are writing a Minecraft client.
|
||||
//!
|
||||
//! [`queue_packet`]: crate::client::Client::queue_packet
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem;
|
||||
|
||||
use anyhow::{anyhow, ensure, Context};
|
||||
use arrayvec::ArrayVec;
|
||||
use bitvec::prelude::*;
|
||||
pub use byte_angle::ByteAngle;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
pub use slot::{Slot, SlotId};
|
||||
use uuid::Uuid;
|
||||
use valence_nbt::Compound;
|
||||
pub use var_int::VarInt;
|
||||
pub use var_long::VarLong;
|
||||
use vek::{Vec2, Vec3, Vec4};
|
||||
|
||||
use crate::entity::EntityId;
|
||||
use crate::nbt;
|
||||
|
||||
mod byte_angle;
|
||||
pub mod codec;
|
||||
pub mod packets;
|
||||
mod slot;
|
||||
mod var_int;
|
||||
mod var_long;
|
||||
|
||||
/// Types that can be written to the Minecraft protocol.
|
||||
pub trait Encode {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()>;
|
||||
|
||||
/// Returns the number of bytes that will be written when [`Self::encode`]
|
||||
/// is called.
|
||||
///
|
||||
/// If [`Self::encode`] results in `Ok`, the exact number of bytes reported
|
||||
/// by this function must be written to the writer argument.
|
||||
///
|
||||
/// If the result is `Err`, then the number of written bytes must be less
|
||||
/// than or equal to the count returned by this function.
|
||||
fn encoded_len(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Types that can be read from the Minecraft protocol.
|
||||
pub trait Decode: Sized {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self>;
|
||||
}
|
||||
|
||||
/// The maximum number of bytes in a single packet.
|
||||
pub const MAX_PACKET_SIZE: i32 = 2097152;
|
||||
|
||||
impl Encode for () {
|
||||
fn encode(&self, _w: &mut impl Write) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for () {
|
||||
fn decode(_r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode, U: Encode> Encode for (T, U) {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.0.encode(w)?;
|
||||
self.1.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.encoded_len() + self.1.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode, U: Decode> Decode for (T, U) {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok((T::decode(r)?, U::decode(r)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for &T {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
(*self).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
(*self).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for bool {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_u8(*self as u8)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for bool {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let n = r.read_u8()?;
|
||||
ensure!(n < 2, "boolean is not 0 or 1");
|
||||
Ok(n == 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u8 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_u8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for u8 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_u8()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i8 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_i8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for i8 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_i8()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u16 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_u16::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for u16 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_u16::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i16 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_i16::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for i16 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_i16::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u32 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_u32::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for u32 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_u32::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i32 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_i32::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for i32 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_i32::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u64 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_u64::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for u64 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_u64::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i64 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_i64::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for i64 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(r.read_i64::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for f32 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
ensure!(
|
||||
self.is_finite(),
|
||||
"attempt to encode non-finite f32 ({})",
|
||||
self
|
||||
);
|
||||
w.write_f32::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
impl Decode for f32 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let f = r.read_f32::<BigEndian>()?;
|
||||
ensure!(f.is_finite(), "attempt to decode non-finite f32 ({f})");
|
||||
Ok(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for f64 {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
ensure!(
|
||||
self.is_finite(),
|
||||
"attempt to encode non-finite f64 ({})",
|
||||
self
|
||||
);
|
||||
w.write_f64::<BigEndian>(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for f64 {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let f = r.read_f64::<BigEndian>()?;
|
||||
ensure!(f.is_finite(), "attempt to decode non-finite f64 ({f})");
|
||||
Ok(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Option<T> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Some(t) => {
|
||||
true.encode(w)?;
|
||||
t.encode(w)
|
||||
}
|
||||
None => false.encode(w),
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1 + match self {
|
||||
Some(t) => t.encoded_len(),
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Option<T> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
if bool::decode(r)? {
|
||||
Ok(Some(T::decode(r)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Box<T> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.as_ref().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Box<T> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Box::new(T::decode(r)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Box<str> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
encode_string_bounded(self, 0, 32767, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Box<str> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(String::decode(r)?.into_boxed_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// An integer with a minimum and maximum value known at compile time. `T` is
|
||||
/// the underlying integer type.
|
||||
///
|
||||
/// If the value is not in bounds, an error is generated while
|
||||
/// encoding or decoding.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct BoundedInt<T, const MIN: i64, const MAX: i64>(pub T);
|
||||
|
||||
impl<T, const MIN: i64, const MAX: i64> BoundedInt<T, MIN, MAX> {
|
||||
pub const fn min_bound(&self) -> i64 {
|
||||
MIN
|
||||
}
|
||||
|
||||
pub const fn max_bound(&self) -> i64 {
|
||||
MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const MIN: i64, const MAX: i64> From<T> for BoundedInt<T, MIN, MAX> {
|
||||
fn from(t: T) -> Self {
|
||||
Self(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const MIN: i64, const MAX: i64> Encode for BoundedInt<T, MIN, MAX>
|
||||
where
|
||||
T: Encode + Copy + Into<i64>,
|
||||
{
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
let val = self.0.into();
|
||||
ensure!(
|
||||
(MIN..=MAX).contains(&val),
|
||||
"Integer is not in bounds while encoding (got {val}, expected {MIN}..={MAX})"
|
||||
);
|
||||
|
||||
self.0.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const MIN: i64, const MAX: i64> Decode for BoundedInt<T, MIN, MAX>
|
||||
where
|
||||
T: Decode + Copy + Into<i64>,
|
||||
{
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let res = T::decode(r)?;
|
||||
let val = res.into();
|
||||
|
||||
ensure!(
|
||||
(MIN..=MAX).contains(&val),
|
||||
"Integer is not in bounds while decoding (got {val}, expected {MIN}..={MAX})"
|
||||
);
|
||||
|
||||
Ok(Self(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for str {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
encode_string_bounded(self, 0, 32767, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(self.len().try_into().unwrap_or(i32::MAX)).encoded_len() + self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for String {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.as_str().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_str().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for String {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
decode_string_bounded(0, 32767, r)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Encode for Cow<'a, str> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.as_ref().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Cow<'static, str> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(String::decode(r)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A string with a minimum and maximum character length known at compile time.
|
||||
///
|
||||
/// If the string is not in bounds, an error is generated while
|
||||
/// encoding or decoding.
|
||||
///
|
||||
/// Note that the length is a count of the characters in the string, not bytes.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
|
||||
pub struct BoundedString<const MIN: usize, const MAX: usize>(pub String);
|
||||
|
||||
impl<const MIN: usize, const MAX: usize> BoundedString<MIN, MAX> {
|
||||
pub const fn min_bound(&self) -> usize {
|
||||
MIN
|
||||
}
|
||||
|
||||
pub const fn max_bound(&self) -> usize {
|
||||
MAX
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MIN: usize, const MAX: usize> Encode for BoundedString<MIN, MAX> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
encode_string_bounded(&self.0, MIN, MAX, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MIN: usize, const MAX: usize> Decode for BoundedString<MIN, MAX> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
decode_string_bounded(MIN, MAX, r).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MIN: usize, const MAX: usize> From<String> for BoundedString<MIN, MAX> {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Encode> Encode for &'a [T] {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
encode_array_bounded(self, 0, usize::MAX, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
let elems_len: usize = self.iter().map(|a| a.encoded_len()).sum();
|
||||
VarInt(self.len().try_into().unwrap_or(i32::MAX)).encoded_len() + elems_len
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Vec<T> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.as_slice().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_slice().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Vec<T> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
decode_array_bounded(0, usize::MAX, r)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Box<[T]> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
encode_array_bounded(self, 0, usize::MAX, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Box<[T]> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
decode_array_bounded(0, usize::MAX, r).map(|v| v.into_boxed_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode, const N: usize> Encode for [T; N] {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
// for t in self {
|
||||
// t.encode(w)?;
|
||||
// }
|
||||
//
|
||||
// Ok(())
|
||||
|
||||
self.as_slice().encode(w)
|
||||
|
||||
// encode_array_bounded(self, N, N, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.iter().map(Encode::encoded_len).sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode, const N: usize> Decode for [T; N] {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
ensure!(VarInt::decode(r)?.0 == N as i32);
|
||||
|
||||
let mut elems = ArrayVec::new();
|
||||
for _ in 0..N {
|
||||
elems.push(T::decode(r)?);
|
||||
}
|
||||
elems.into_inner().map_err(|_| unreachable!())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Vec2<T> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.x.encode(w)?;
|
||||
self.y.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.x.encoded_len() + self.y.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Vec3<T> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.x.encode(w)?;
|
||||
self.y.encode(w)?;
|
||||
self.z.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.x.encoded_len() + self.y.encoded_len() + self.z.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Vec4<T> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.x.encode(w)?;
|
||||
self.y.encode(w)?;
|
||||
self.z.encode(w)?;
|
||||
self.w.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.x.encoded_len() + self.y.encoded_len() + self.z.encoded_len() + self.w.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Vec2<T> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Vec2::new(T::decode(r)?, T::decode(r)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Vec3<T> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Vec3::new(T::decode(r)?, T::decode(r)?, T::decode(r)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> Decode for Vec4<T> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Vec4::new(
|
||||
T::decode(r)?,
|
||||
T::decode(r)?,
|
||||
T::decode(r)?,
|
||||
T::decode(r)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// An array with a minimum and maximum character length known at compile time.
|
||||
///
|
||||
/// If the array is not in bounds, an error is generated while
|
||||
/// encoding or decoding.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)]
|
||||
pub struct BoundedArray<T, const MIN: usize, const MAX: usize>(pub Vec<T>);
|
||||
|
||||
impl<T: Encode, const MIN: usize, const MAX: usize> Encode for BoundedArray<T, MIN, MAX> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
encode_array_bounded(&self.0, MIN, MAX, w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.as_slice().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode, const MIN: usize, const MAX: usize> Decode for BoundedArray<T, MIN, MAX> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
decode_array_bounded(MIN, MAX, r).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const MIN: usize, const MAX: usize> From<Vec<T>> for BoundedArray<T, MIN, MAX> {
|
||||
fn from(v: Vec<T>) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Uuid {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
w.write_u128::<BigEndian>(self.as_u128())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Uuid {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Uuid::from_u128(r.read_u128::<BigEndian>()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Compound {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
Ok(nbt::to_binary_writer(w, self, "")?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.binary_encoded_len("")
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Compound {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let (nbt, _) = nbt::from_binary_slice(r)?;
|
||||
Ok(nbt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for BitVec<u64> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.as_raw_slice().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_raw_slice().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for BitVec<u64> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
BitVec::try_from_vec(Vec::decode(r)?)
|
||||
.map_err(|_| anyhow!("Array is too long for bit vector"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for BitBox<u64> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.as_raw_slice().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_raw_slice().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for BitBox<u64> {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
BitVec::decode(r).map(|v| v.into_boxed_bitslice())
|
||||
}
|
||||
}
|
||||
|
||||
/// When decoding, reads the rest of the data in a packet and stuffs it into a
|
||||
/// `Vec<u8>`. When encoding, the data is inserted into the packet with no
|
||||
/// length prefix.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RawBytes(pub Vec<u8>);
|
||||
|
||||
impl Decode for RawBytes {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let mut buf = Vec::new();
|
||||
r.read_to_end(&mut buf)?;
|
||||
Ok(RawBytes(buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for RawBytes {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
Ok(w.write_all(&self.0)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Option<EntityId> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Some(id) => VarInt(
|
||||
id.to_network_id()
|
||||
.checked_add(1)
|
||||
.context("i32::MAX is unrepresentable as an optional VarInt")?,
|
||||
),
|
||||
None => VarInt(0),
|
||||
}
|
||||
.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_array_bounded<T: Encode>(
|
||||
s: &[T],
|
||||
min: usize,
|
||||
max: usize,
|
||||
w: &mut impl Write,
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(min <= max);
|
||||
|
||||
let len = s.len();
|
||||
|
||||
ensure!(
|
||||
(min..=max).contains(&len),
|
||||
"Length of array is out of bounds while encoding (got {len}, expected {min}..={max})"
|
||||
);
|
||||
|
||||
ensure!(
|
||||
len <= i32::MAX as usize,
|
||||
"Length of array ({len}) exceeds i32::MAX"
|
||||
);
|
||||
|
||||
VarInt(len as i32).encode(w)?;
|
||||
for t in s {
|
||||
t.encode(w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn encode_string_bounded(
|
||||
s: &str,
|
||||
min: usize,
|
||||
max: usize,
|
||||
w: &mut impl Write,
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(min <= max, "Bad min and max");
|
||||
|
||||
let char_count = s.chars().count();
|
||||
|
||||
ensure!(
|
||||
(min..=max).contains(&char_count),
|
||||
"Char count of string is out of bounds while encoding (got {char_count}, expected \
|
||||
{min}..={max})"
|
||||
);
|
||||
|
||||
encode_array_bounded(s.as_bytes(), 0, usize::MAX, w)
|
||||
}
|
||||
|
||||
pub(crate) fn decode_string_bounded(
|
||||
min: usize,
|
||||
max: usize,
|
||||
r: &mut &[u8],
|
||||
) -> anyhow::Result<String> {
|
||||
assert!(min <= max);
|
||||
|
||||
let bytes = decode_array_bounded(min, max.saturating_mul(4), r)?;
|
||||
let string = String::from_utf8(bytes)?;
|
||||
|
||||
let char_count = string.chars().count();
|
||||
ensure!(
|
||||
(min..=max).contains(&char_count),
|
||||
"Char count of string is out of bounds while decoding (got {char_count}, expected \
|
||||
{min}..={max}"
|
||||
);
|
||||
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
pub(crate) fn decode_array_bounded<T: Decode>(
|
||||
min: usize,
|
||||
max: usize,
|
||||
r: &mut &[u8],
|
||||
) -> anyhow::Result<Vec<T>> {
|
||||
assert!(min <= max);
|
||||
|
||||
let len = VarInt::decode(r)?.0;
|
||||
ensure!(
|
||||
len >= 0 && (min..=max).contains(&(len as usize)),
|
||||
"Length of array is out of bounds while decoding (got {len}, needed {min}..={max})",
|
||||
);
|
||||
|
||||
// Don't allocate more than what would roughly fit in a single packet in case we
|
||||
// get a malicious array length.
|
||||
let cap = (MAX_PACKET_SIZE as usize / mem::size_of::<T>().max(1)).min(len as usize);
|
||||
|
||||
let mut res = Vec::with_capacity(cap);
|
||||
for _ in 0..len {
|
||||
res.push(T::decode(r)?);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
|
@ -1,466 +0,0 @@
|
|||
//! Packet definitions and related types.
|
||||
//!
|
||||
//! See <https://wiki.vg/Protocol> for more packet documentation.
|
||||
|
||||
#![macro_use]
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use bitvec::prelude::BitVec;
|
||||
use num::{One, Zero};
|
||||
use paste::paste;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use vek::Vec3;
|
||||
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::ident::Ident;
|
||||
use crate::nbt::Compound;
|
||||
use crate::protocol::{
|
||||
BoundedArray, BoundedInt, BoundedString, ByteAngle, Decode, Encode, RawBytes, Slot, VarInt,
|
||||
VarLong,
|
||||
};
|
||||
use crate::text::Text;
|
||||
use crate::username::Username;
|
||||
|
||||
/// Provides the name of a packet for debugging purposes.
|
||||
pub trait PacketName {
|
||||
/// The name of this packet.
|
||||
fn packet_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Trait for types that can be written to the Minecraft protocol as a complete
|
||||
/// packet.
|
||||
///
|
||||
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
|
||||
/// the body of the packet.
|
||||
pub trait EncodePacket: PacketName + fmt::Debug {
|
||||
/// Writes a packet to the Minecraft protocol, including its packet ID.
|
||||
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()>;
|
||||
fn encoded_packet_len(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Trait for types that can be read from the Minecraft protocol as a complete
|
||||
/// packet.
|
||||
///
|
||||
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
|
||||
/// the body of the packet.
|
||||
pub trait DecodePacket: Sized + PacketName + fmt::Debug {
|
||||
/// Reads a packet from the Minecraft protocol, including its packet ID.
|
||||
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self>;
|
||||
}
|
||||
|
||||
/// Defines a struct which implements [`Encode`] and [`Decode`].
|
||||
///
|
||||
/// The fields of the struct are encoded and decoded in the order they are
|
||||
/// defined.
|
||||
macro_rules! def_struct {
|
||||
(
|
||||
$(#[$struct_attrs:meta])*
|
||||
$name:ident {
|
||||
$(
|
||||
$(#[$field_attrs:meta])*
|
||||
$field:ident: $typ:ty
|
||||
),* $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Clone, Debug)]
|
||||
$(#[$struct_attrs])*
|
||||
pub struct $name {
|
||||
$(
|
||||
$(#[$field_attrs])*
|
||||
pub $field: $typ,
|
||||
)*
|
||||
}
|
||||
|
||||
impl Encode for $name {
|
||||
fn encode(&self, _w: &mut impl Write) -> anyhow::Result<()> {
|
||||
$(
|
||||
Encode::encode(&self.$field, _w)
|
||||
.context(concat!("failed to write field `", stringify!($field), "` from struct `", stringify!($name), "`"))?;
|
||||
)*
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
0
|
||||
$(
|
||||
+ self.$field.encoded_len()
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for $name {
|
||||
fn decode(_r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
$(
|
||||
let $field: $typ = Decode::decode(_r)
|
||||
.context(concat!("failed to read field `", stringify!($field), "` from struct `", stringify!($name), "`"))?;
|
||||
)*
|
||||
|
||||
Ok(Self {
|
||||
$(
|
||||
$field,
|
||||
)*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: https://github.com/rust-lang/rust/issues/48214
|
||||
//impl Copy for $name
|
||||
//where
|
||||
// $(
|
||||
// $typ: Copy
|
||||
// )*
|
||||
//{}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines an enum which implements [`Encode`] and [`Decode`].
|
||||
///
|
||||
/// The enum tag is encoded and decoded first, followed by the appropriate
|
||||
/// variant.
|
||||
macro_rules! def_enum {
|
||||
(
|
||||
$(#[$enum_attrs:meta])*
|
||||
$name:ident: $tag_ty:ty {
|
||||
$(
|
||||
$(#[$variant_attrs:meta])*
|
||||
$variant:ident$(: $typ:ty)? = $lit:literal
|
||||
),* $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Clone, Debug)]
|
||||
$(#[$enum_attrs])*
|
||||
pub enum $name {
|
||||
$(
|
||||
$(#[$variant_attrs])*
|
||||
$variant$(($typ))?,
|
||||
)*
|
||||
}
|
||||
|
||||
impl Encode for $name {
|
||||
fn encode(&self, _w: &mut impl Write) -> anyhow::Result<()> {
|
||||
match self {
|
||||
$(
|
||||
if_typ_is_empty_pat!($($typ)?, $name::$variant, $name::$variant(val)) => {
|
||||
<$tag_ty>::encode(&$lit.into(), _w)
|
||||
.context(concat!("failed to write enum tag for `", stringify!($name), "`"))?;
|
||||
|
||||
if_typ_is_empty_expr!($($typ)?, Ok(()), {
|
||||
Encode::encode(val, _w)
|
||||
.context(concat!("failed to write variant `", stringify!($variant), "` from enum `", stringify!($name), "`"))
|
||||
})
|
||||
},
|
||||
)*
|
||||
|
||||
// Need this because references to uninhabited enums are considered inhabited.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!("uninhabited enum?")
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
match self {
|
||||
$(
|
||||
if_typ_is_empty_pat!($($typ)?, $name::$variant, $name::$variant(val)) => {
|
||||
<$tag_ty>::encoded_len(&$lit.into()) +
|
||||
if_typ_is_empty_expr!($($typ)?, 0, Encode::encoded_len(val))
|
||||
}
|
||||
)*
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!("uninhabited enum?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for $name {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let tag_ctx = concat!("failed to read enum tag for `", stringify!($name), "`");
|
||||
let tag = <$tag_ty>::decode(r).context(tag_ctx)?.into();
|
||||
match tag {
|
||||
$(
|
||||
$lit => {
|
||||
if_typ_is_empty_expr!($($typ)?, Ok($name::$variant), {
|
||||
$(
|
||||
let res: $typ = Decode::decode(r)
|
||||
.context(concat!("failed to read variant `", stringify!($variant), "` from enum `", stringify!($name), "`"))?;
|
||||
Ok($name::$variant(res))
|
||||
)?
|
||||
})
|
||||
}
|
||||
)*
|
||||
_ => bail!(concat!("bad tag value for enum `", stringify!($name), "`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! if_typ_is_empty_expr {
|
||||
(, $t:expr, $f:expr) => {
|
||||
$t
|
||||
};
|
||||
($typ:ty, $t:expr, $f:expr) => {
|
||||
$f
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! if_typ_is_empty_pat {
|
||||
(, $t:pat, $f:pat) => {
|
||||
$t
|
||||
};
|
||||
($typ:ty, $t:pat, $f:pat) => {
|
||||
$f
|
||||
};
|
||||
}
|
||||
|
||||
/// Defines a bitfield struct which implements [`Encode`] and [`Decode`].
|
||||
macro_rules! def_bitfield {
|
||||
(
|
||||
$(#[$struct_attrs:meta])*
|
||||
$name:ident: $inner_ty:ty {
|
||||
$(
|
||||
$(#[$bit_attrs:meta])*
|
||||
$bit:ident = $offset:literal
|
||||
),* $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
$(#[$struct_attrs])*
|
||||
pub struct $name($inner_ty);
|
||||
|
||||
impl $name {
|
||||
pub fn new(
|
||||
$(
|
||||
$bit: bool,
|
||||
)*
|
||||
) -> Self {
|
||||
let mut res = Self(Default::default());
|
||||
paste! {
|
||||
$(
|
||||
res = res.[<set_ $bit:snake>]($bit);
|
||||
)*
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
paste! {
|
||||
$(
|
||||
#[doc = "Gets the " $bit " bit on this bitfield.\n"]
|
||||
$(#[$bit_attrs])*
|
||||
pub fn $bit(self) -> bool {
|
||||
self.0 & <$inner_ty>::one() << <$inner_ty>::from($offset) != <$inner_ty>::zero()
|
||||
}
|
||||
|
||||
#[doc = "Sets the " $bit " bit on this bitfield.\n"]
|
||||
$(#[$bit_attrs])*
|
||||
#[must_use]
|
||||
pub fn [<set_ $bit:snake>](self, $bit: bool) -> Self {
|
||||
let mask = <$inner_ty>::one() << <$inner_ty>::from($offset);
|
||||
if $bit {
|
||||
Self(self.0 | mask)
|
||||
} else {
|
||||
Self(self.0 & !mask)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut s = f.debug_struct(stringify!($name));
|
||||
paste! {
|
||||
$(
|
||||
s.field(stringify!($bit), &self. $bit());
|
||||
)*
|
||||
}
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for $name {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.0.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for $name {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
<$inner_ty>::decode(r).map(Self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines an enum of packets.
|
||||
///
|
||||
/// An impl for [`EncodePacket`] and [`DecodePacket`] is defined for each
|
||||
/// supplied packet.
|
||||
macro_rules! def_packet_group {
|
||||
(
|
||||
$(#[$attrs:meta])*
|
||||
$group_name:ident {
|
||||
$($packet:ident = $id:literal),* $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Clone)]
|
||||
$(#[$attrs])*
|
||||
pub enum $group_name {
|
||||
$($packet($packet)),*
|
||||
}
|
||||
|
||||
$(
|
||||
impl From<$packet> for $group_name {
|
||||
fn from(p: $packet) -> Self {
|
||||
Self::$packet(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketName for $packet {
|
||||
fn packet_name(&self) -> &'static str {
|
||||
stringify!($packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodePacket for $packet {
|
||||
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
VarInt($id).encode(w).context("failed to write packet ID")?;
|
||||
self.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_packet_len(&self) -> usize {
|
||||
VarInt($id).encoded_len() + self.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodePacket for $packet {
|
||||
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let packet_id = VarInt::decode(r).context("failed to read packet ID")?.0;
|
||||
|
||||
ensure!(
|
||||
$id == packet_id,
|
||||
"bad packet ID (expected {}, got {packet_id}",
|
||||
$id
|
||||
);
|
||||
Self::decode(r)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
impl PacketName for $group_name {
|
||||
fn packet_name(&self) -> &'static str {
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => pkt.packet_name(),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodePacket for $group_name {
|
||||
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let packet_id = VarInt::decode(r)
|
||||
.context(concat!("failed to read ", stringify!($group_name), " packet ID"))?.0;
|
||||
|
||||
match packet_id {
|
||||
$(
|
||||
$id => {
|
||||
let pkt = $packet::decode(r)?;
|
||||
Ok(Self::$packet(pkt))
|
||||
}
|
||||
)*
|
||||
id => bail!(concat!("unknown ", stringify!($group_name), " packet ID {}"), id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodePacket for $group_name {
|
||||
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => {
|
||||
VarInt($id)
|
||||
.encode(w)
|
||||
.context(concat!(
|
||||
"failed to write ",
|
||||
stringify!($group_name),
|
||||
" packet ID for ",
|
||||
stringify!($packet_name)
|
||||
))?;
|
||||
pkt.encode(w)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_packet_len(&self) -> usize {
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => VarInt($id).encoded_len() + pkt.encoded_len(),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for $group_name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut t = f.debug_tuple(stringify!($group_name));
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => t.field(pkt),
|
||||
)*
|
||||
};
|
||||
t.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Must be below the macro_rules!.
|
||||
pub mod c2s;
|
||||
pub mod s2c;
|
||||
|
||||
def_struct! {
|
||||
#[derive(PartialEq, Serialize, Deserialize)]
|
||||
Property {
|
||||
name: String,
|
||||
value: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
signature: Option<String>
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PublicKeyData {
|
||||
timestamp: u64,
|
||||
public_key: Vec<u8>,
|
||||
signature: Vec<u8>,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
TestPacket {
|
||||
first: String,
|
||||
second: Vec<u16>,
|
||||
third: u64
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
TestPacketGroup {
|
||||
TestPacket = 12345,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,775 +0,0 @@
|
|||
//! Client to server packets.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub mod handshake {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
Handshake {
|
||||
protocol_version: VarInt,
|
||||
// by the minecraft protocol this is specified as a BoundedString<0, 255> but due
|
||||
// issues with Bungeecord ip forwarding this limit is removed here and checked when handling the handshake
|
||||
server_address: String,
|
||||
server_port: u16,
|
||||
next_state: HandshakeNextState,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
HandshakeNextState: VarInt {
|
||||
Status = 1,
|
||||
Login = 2,
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
C2sHandshakePacket {
|
||||
Handshake = 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod status {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
StatusRequest {}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PingRequest {
|
||||
payload: u64
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
C2sStatusPacket {
|
||||
StatusRequest = 0,
|
||||
PingRequest = 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod login {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
LoginStart {
|
||||
username: Username<String>,
|
||||
sig_data: Option<PublicKeyData>,
|
||||
profile_id: Option<Uuid>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EncryptionResponse {
|
||||
shared_secret: BoundedArray<u8, 16, 128>,
|
||||
token_or_sig: VerifyTokenOrMsgSig,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
VerifyTokenOrMsgSig: u8 {
|
||||
VerifyToken: BoundedArray<u8, 16, 128> = 1,
|
||||
MsgSig: MessageSignature = 0,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
MessageSignature {
|
||||
salt: u64,
|
||||
sig: Vec<u8>, // TODO: bounds?
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
LoginPluginResponse {
|
||||
message_id: VarInt,
|
||||
data: Option<RawBytes>,
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
C2sLoginPacket {
|
||||
LoginStart = 0,
|
||||
EncryptionResponse = 1,
|
||||
LoginPluginResponse = 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod play {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
ConfirmTeleport {
|
||||
teleport_id: VarInt
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
QueryBlockEntityTag {
|
||||
transaction_id: VarInt,
|
||||
location: BlockPos,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
ChangeDifficulty: i8 {
|
||||
Peaceful = 0,
|
||||
Easy = 1,
|
||||
Normal = 2,
|
||||
Hard = 3,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
MessageAcknowledgmentList {
|
||||
entries: Vec<MessageAcknowledgmentEntry>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
MessageAcknowledgment {
|
||||
last_seen: MessageAcknowledgmentList,
|
||||
last_received: Option<MessageAcknowledgmentEntry>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
MessageAcknowledgmentEntry {
|
||||
profile_id: Uuid,
|
||||
signature: Vec<u8>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ArgumentSignatureEntry {
|
||||
name: BoundedString<0, 16>,
|
||||
signature: Vec<u8>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ChatCommand {
|
||||
command: BoundedString<0, 256>,
|
||||
timestamp: u64,
|
||||
salt: u64,
|
||||
arg_sig: Vec<ArgumentSignatureEntry>,
|
||||
signed_preview: bool,
|
||||
acknowledgement: MessageAcknowledgment,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ChatMessage {
|
||||
message: BoundedString<0, 256>,
|
||||
timestamp: u64,
|
||||
salt: u64,
|
||||
signature: Vec<u8>,
|
||||
signed_preview: bool,
|
||||
acknowledgement: MessageAcknowledgment,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ChatPreviewC2s {
|
||||
query: i32, // TODO: is this an i32 or a varint?
|
||||
message: BoundedString<0, 256>,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
ClientCommand: 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! {
|
||||
ClientInformation {
|
||||
/// e.g. en_US
|
||||
locale: BoundedString<0, 16>,
|
||||
/// Client-side render distance in chunks.
|
||||
view_distance: BoundedInt<u8, 2, 32>,
|
||||
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! {
|
||||
CommandSuggestionsRequest {
|
||||
transaction_id: VarInt,
|
||||
/// Text behind the cursor without the '/'.
|
||||
text: BoundedString<0, 32500>
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ClickContainerButton {
|
||||
window_id: i8,
|
||||
button_id: i8,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ClickContainer {
|
||||
window_id: u8,
|
||||
state_id: VarInt,
|
||||
slot_idx: i16,
|
||||
button: i8,
|
||||
mode: ClickContainerMode,
|
||||
slots: Vec<(i16, Slot)>,
|
||||
carried_item: Slot,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
#[derive(Copy, PartialEq, Eq)]
|
||||
ClickContainerMode: VarInt {
|
||||
Click = 0,
|
||||
ShiftClick = 1,
|
||||
Hotbar = 2,
|
||||
CreativeMiddleClick = 3,
|
||||
DropKey = 4,
|
||||
Drag = 5,
|
||||
DoubleClick = 6,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
CloseContainerC2s {
|
||||
window_id: u8,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PluginMessageC2s {
|
||||
channel: Ident<String>,
|
||||
data: RawBytes,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EditBook {
|
||||
slot: VarInt,
|
||||
entries: Vec<String>,
|
||||
title: Option<String>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
QueryEntityTag {
|
||||
transaction_id: VarInt,
|
||||
entity_id: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
Interact {
|
||||
entity_id: VarInt,
|
||||
kind: InteractKind,
|
||||
sneaking: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
InteractKind: VarInt {
|
||||
Interact: Hand = 0,
|
||||
Attack = 1,
|
||||
InteractAt: (Vec3<f32>, Hand) = 2
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
#[derive(Copy, PartialEq, Eq)]
|
||||
Hand: VarInt {
|
||||
Main = 0,
|
||||
Off = 1,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
JigsawGenerate {
|
||||
location: BlockPos,
|
||||
levels: VarInt,
|
||||
keep_jigsaws: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
KeepAliveC2s {
|
||||
id: i64,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
LockDifficulty {
|
||||
locked: bool
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetPlayerPosition {
|
||||
position: Vec3<f64>,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetPlayerPositionAndRotation {
|
||||
// Absolute position
|
||||
position: Vec3<f64>,
|
||||
/// Absolute rotation on X axis in degrees.
|
||||
yaw: f32,
|
||||
/// Absolute rotation on Y axis in degrees.
|
||||
pitch: f32,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetPlayerRotation {
|
||||
/// Absolute rotation on X axis in degrees.
|
||||
yaw: f32,
|
||||
/// Absolute rotation on Y axis in degrees.
|
||||
pitch: f32,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetPlayerOnGround {
|
||||
on_ground: bool
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
MoveVehicleC2s {
|
||||
/// Absolute position
|
||||
position: Vec3<f64>,
|
||||
/// Degrees
|
||||
yaw: f32,
|
||||
/// Degrees
|
||||
pitch: f32,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PaddleBoat {
|
||||
left_paddle_turning: bool,
|
||||
right_paddle_turning: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PickItem {
|
||||
slot_to_use: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PlaceRecipe {
|
||||
window_id: i8,
|
||||
recipe: Ident<String>,
|
||||
make_all: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
PlayerAbilitiesC2s: i8 {
|
||||
NotFlying = 0,
|
||||
Flying = 0b10,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PlayerAction {
|
||||
status: DiggingStatus,
|
||||
location: BlockPos,
|
||||
face: BlockFace,
|
||||
sequence: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
DiggingStatus: VarInt {
|
||||
StartedDigging = 0,
|
||||
CancelledDigging = 1,
|
||||
FinishedDigging = 2,
|
||||
DropItemStack = 3,
|
||||
DropItem = 4,
|
||||
ShootArrowOrFinishEating = 5,
|
||||
SwapItemInHand = 6,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
#[derive(Copy, PartialEq, Eq)]
|
||||
BlockFace: i8 {
|
||||
/// -Y
|
||||
Bottom = 0,
|
||||
/// +Y
|
||||
Top = 1,
|
||||
/// -Z
|
||||
North = 2,
|
||||
/// +Z
|
||||
South = 3,
|
||||
/// -X
|
||||
West = 4,
|
||||
/// +X
|
||||
East = 5,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PlayerCommand {
|
||||
entity_id: VarInt,
|
||||
action_id: PlayerCommandId,
|
||||
jump_boost: BoundedInt<VarInt, 0, 100>,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
PlayerCommandId: VarInt {
|
||||
StartSneaking = 0,
|
||||
StopSneaking = 1,
|
||||
LeaveBed = 2,
|
||||
StartSprinting = 3,
|
||||
StopSprinting = 4,
|
||||
StartJumpWithHorse = 5,
|
||||
StopJumpWithHorse = 6,
|
||||
OpenHorseInventory = 7,
|
||||
StartFlyingWithElytra = 8,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PlayerInput {
|
||||
sideways: f32,
|
||||
forward: f32,
|
||||
flags: PlayerInputFlags,
|
||||
}
|
||||
}
|
||||
|
||||
def_bitfield! {
|
||||
PlayerInputFlags: u8 {
|
||||
jump = 0,
|
||||
unmount = 1,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PongPlay {
|
||||
id: i32,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ChangeRecipeBookSettings {
|
||||
book_id: RecipeBookId,
|
||||
book_open: bool,
|
||||
filter_active: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
RecipeBookId: VarInt {
|
||||
Crafting = 0,
|
||||
Furnace = 1,
|
||||
BlastFurnace = 2,
|
||||
Smoker = 3,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetSeenRecipe {
|
||||
recipe_id: Ident<String>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
RenameItem {
|
||||
item_name: BoundedString<0, 50>,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
#[derive(Copy, PartialEq, Eq)]
|
||||
ResourcePackC2s: VarInt {
|
||||
SuccessfullyLoaded = 0,
|
||||
Declined = 1,
|
||||
FailedDownload = 2,
|
||||
Accepted = 3,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
SeenAdvancements: VarInt {
|
||||
OpenedTab: Ident<String> = 0,
|
||||
ClosedScreen = 1,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SelectTrade {
|
||||
selected_slot: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetBeaconEffect {
|
||||
// TODO: potion ids
|
||||
primary_effect: Option<VarInt>,
|
||||
secondary_effect: Option<VarInt>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetHeldItemS2c {
|
||||
slot: BoundedInt<i16, 0, 8>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ProgramCommandBlock {
|
||||
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! {
|
||||
ProgramCommandBlockMinecart {
|
||||
entity_id: VarInt,
|
||||
command: String,
|
||||
track_output: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetCreativeModeSlot {
|
||||
slot: i16,
|
||||
clicked_item: Slot,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ProgramJigsawBlock {
|
||||
location: BlockPos,
|
||||
name: Ident<String>,
|
||||
target: Ident<String>,
|
||||
pool: Ident<String>,
|
||||
final_state: String,
|
||||
joint_type: String,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ProgramStructureBlock {
|
||||
location: BlockPos,
|
||||
action: StructureBlockAction,
|
||||
mode: StructureBlockMode,
|
||||
name: String,
|
||||
offset_xyz: [BoundedInt<i8, -32, 32>; 3],
|
||||
size_xyz: [BoundedInt<i8, 0, 32>; 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 {
|
||||
location: BlockPos,
|
||||
lines: [BoundedString<0, 384>; 4],
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SwingArm {
|
||||
hand: Hand,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
TeleportToEntity {
|
||||
target: Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UseItemOn {
|
||||
hand: Hand,
|
||||
location: BlockPos,
|
||||
face: BlockFace,
|
||||
cursor_pos: Vec3<f32>,
|
||||
head_inside_block: bool,
|
||||
sequence: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UseItem {
|
||||
hand: Hand,
|
||||
sequence: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
C2sPlayPacket {
|
||||
ConfirmTeleport = 0,
|
||||
QueryBlockEntityTag = 1,
|
||||
ChangeDifficulty = 2,
|
||||
MessageAcknowledgment = 3,
|
||||
ChatCommand = 4,
|
||||
ChatMessage = 5,
|
||||
ChatPreviewC2s = 6,
|
||||
ClientCommand = 7,
|
||||
ClientInformation = 8,
|
||||
CommandSuggestionsRequest = 9,
|
||||
ClickContainerButton = 10,
|
||||
ClickContainer = 11,
|
||||
CloseContainerC2s = 12,
|
||||
PluginMessageC2s = 13,
|
||||
EditBook = 14,
|
||||
QueryEntityTag = 15,
|
||||
Interact = 16,
|
||||
JigsawGenerate = 17,
|
||||
KeepAliveC2s = 18,
|
||||
LockDifficulty = 19,
|
||||
SetPlayerPosition = 20,
|
||||
SetPlayerPositionAndRotation = 21,
|
||||
SetPlayerRotation = 22,
|
||||
SetPlayerOnGround = 23,
|
||||
MoveVehicleC2s = 24,
|
||||
PaddleBoat = 25,
|
||||
PickItem = 26,
|
||||
PlaceRecipe = 27,
|
||||
PlayerAbilitiesC2s = 28,
|
||||
PlayerAction = 29,
|
||||
PlayerCommand = 30,
|
||||
PlayerInput = 31,
|
||||
PongPlay = 32,
|
||||
ChangeRecipeBookSettings = 33,
|
||||
SetSeenRecipe = 34,
|
||||
RenameItem = 35,
|
||||
ResourcePackC2s = 36,
|
||||
SeenAdvancements = 37,
|
||||
SelectTrade = 38,
|
||||
SetBeaconEffect = 39,
|
||||
SetHeldItemS2c = 40,
|
||||
ProgramCommandBlock = 41,
|
||||
ProgramCommandBlockMinecart = 42,
|
||||
SetCreativeModeSlot = 43,
|
||||
ProgramJigsawBlock = 44,
|
||||
ProgramStructureBlock = 45,
|
||||
UpdateSign = 46,
|
||||
SwingArm = 47,
|
||||
TeleportToEntity = 48,
|
||||
UseItemOn = 49,
|
||||
UseItem = 50,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,795 +0,0 @@
|
|||
//! Server to client packets.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub mod status {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
StatusResponse {
|
||||
json_response: String
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PingResponse {
|
||||
/// Should be the same as the payload from ping.
|
||||
payload: u64
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
S2cStatusPacket {
|
||||
StatusResponse = 0,
|
||||
PingResponse = 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod login {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
DisconnectLogin {
|
||||
reason: Text,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EncryptionRequest {
|
||||
/// Currently unused
|
||||
server_id: BoundedString<0, 20>,
|
||||
/// The RSA public key
|
||||
public_key: Vec<u8>,
|
||||
verify_token: BoundedArray<u8, 4, 16>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
LoginSuccess {
|
||||
uuid: Uuid,
|
||||
username: Username<String>,
|
||||
properties: Vec<Property>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetCompression {
|
||||
threshold: VarInt
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
LoginPluginRequest {
|
||||
message_id: VarInt,
|
||||
channel: Ident<String>,
|
||||
data: RawBytes,
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
S2cLoginPacket {
|
||||
DisconnectLogin = 0,
|
||||
EncryptionRequest = 1,
|
||||
LoginSuccess = 2,
|
||||
SetCompression = 3,
|
||||
LoginPluginRequest = 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod play {
|
||||
use super::*;
|
||||
|
||||
def_struct! {
|
||||
SpawnEntity {
|
||||
entity_id: VarInt,
|
||||
object_uuid: Uuid,
|
||||
kind: VarInt,
|
||||
position: Vec3<f64>,
|
||||
pitch: ByteAngle,
|
||||
yaw: ByteAngle,
|
||||
head_yaw: ByteAngle,
|
||||
data: VarInt,
|
||||
velocity: Vec3<i16>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SpawnExperienceOrb {
|
||||
entity_id: VarInt,
|
||||
position: Vec3<f64>,
|
||||
count: i16,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SpawnPlayer {
|
||||
entity_id: VarInt,
|
||||
player_uuid: Uuid,
|
||||
position: Vec3<f64>,
|
||||
yaw: ByteAngle,
|
||||
pitch: ByteAngle,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EntityAnimationS2c {
|
||||
entity_id: VarInt,
|
||||
animation: u8,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
AcknowledgeBlockChange {
|
||||
sequence: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetBlockDestroyStage {
|
||||
entity_id: VarInt,
|
||||
location: BlockPos,
|
||||
destroy_stage: BoundedInt<u8, 0, 10>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
BlockEntityData {
|
||||
location: BlockPos,
|
||||
kind: VarInt, // TODO: use enum here
|
||||
data: Compound,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
BlockAction {
|
||||
location: BlockPos,
|
||||
action_id: u8,
|
||||
action_param: u8,
|
||||
block_type: VarInt, // TODO: use BlockType type.
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
BlockUpdate {
|
||||
location: BlockPos,
|
||||
block_id: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
BossBar {
|
||||
uuid: Uuid,
|
||||
action: BossBarAction,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
BossBarAction: VarInt {
|
||||
Add: BossBarActionAdd = 0,
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
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! {
|
||||
SetDifficulty {
|
||||
difficulty: Difficulty,
|
||||
locked: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
Difficulty: u8 {
|
||||
Peaceful = 0,
|
||||
Easy = 1,
|
||||
Normal = 2,
|
||||
Hard = 3,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ClearTitles {
|
||||
reset: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetContainerContent {
|
||||
window_id: u8,
|
||||
state_id: VarInt,
|
||||
slots: Vec<Slot>,
|
||||
carried_item: Slot,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetContainerProperty {
|
||||
window_id: u8,
|
||||
property: i16,
|
||||
value: i16,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetContainerSlot {
|
||||
window_id: i8,
|
||||
state_id: VarInt,
|
||||
slot_idx: i16,
|
||||
slot_data: Slot,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetCooldown {
|
||||
item_id: VarInt,
|
||||
cooldown_ticks: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
SoundCategory: VarInt {
|
||||
Master = 0,
|
||||
Music = 1,
|
||||
Record = 2,
|
||||
Weather = 3,
|
||||
Block = 4,
|
||||
Hostile = 5,
|
||||
Neutral = 6,
|
||||
Player = 7,
|
||||
Ambient = 8,
|
||||
Voice = 9,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PluginMessageS2c {
|
||||
channel: Ident<String>,
|
||||
data: RawBytes,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
CustomSoundEffect {
|
||||
name: Ident<String>,
|
||||
category: SoundCategory,
|
||||
position: Vec3<i32>,
|
||||
volume: f32,
|
||||
pitch: f32,
|
||||
seed: i64,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
DisconnectPlay {
|
||||
reason: Text,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EntityEvent {
|
||||
entity_id: i32,
|
||||
entity_status: u8,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UnloadChunk {
|
||||
chunk_x: i32,
|
||||
chunk_z: i32
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
GameEvent {
|
||||
reason: GameStateChangeReason,
|
||||
value: f32,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
GameStateChangeReason: 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! {
|
||||
WorldBorderInitialize {
|
||||
x: f64,
|
||||
z: f64,
|
||||
old_diameter: f64,
|
||||
new_diameter: f64,
|
||||
speed: VarLong,
|
||||
portal_teleport_boundary: VarInt,
|
||||
warning_blocks: VarInt,
|
||||
warning_time: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
KeepAliveS2c {
|
||||
id: i64,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ChunkDataAndUpdateLight {
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
heightmaps: Compound,
|
||||
blocks_and_biomes: Vec<u8>,
|
||||
block_entities: Vec<ChunkDataBlockEntity>,
|
||||
trust_edges: bool,
|
||||
sky_light_mask: BitVec<u64>,
|
||||
block_light_mask: BitVec<u64>,
|
||||
empty_sky_light_mask: BitVec<u64>,
|
||||
empty_block_light_mask: BitVec<u64>,
|
||||
sky_light_arrays: Vec<[u8; 2048]>,
|
||||
block_light_arrays: Vec<[u8; 2048]>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ChunkDataBlockEntity {
|
||||
packed_xz: i8,
|
||||
y: i16,
|
||||
kind: VarInt,
|
||||
data: Compound,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
LoginPlay {
|
||||
/// Entity ID of the joining player
|
||||
entity_id: i32,
|
||||
is_hardcore: bool,
|
||||
gamemode: GameMode,
|
||||
previous_gamemode: GameMode,
|
||||
dimension_names: Vec<Ident<String>>,
|
||||
/// Contains information about dimensions, biomes, and chats.
|
||||
registry_codec: Compound,
|
||||
/// The name of the dimension type being spawned into.
|
||||
dimension_type_name: Ident<String>,
|
||||
/// The name of the dimension being spawned into.
|
||||
dimension_name: Ident<String>,
|
||||
/// 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<VarInt, 2, 32>,
|
||||
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,
|
||||
last_death_location: Option<(Ident<String>, BlockPos)>,
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
||||
GameMode: u8 {
|
||||
#[default]
|
||||
Survival = 0,
|
||||
Creative = 1,
|
||||
Adventure = 2,
|
||||
Spectator = 3,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UpdateEntityPosition {
|
||||
entity_id: VarInt,
|
||||
delta: Vec3<i16>,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UpdateEntityPositionAndRotation {
|
||||
entity_id: VarInt,
|
||||
delta: Vec3<i16>,
|
||||
yaw: ByteAngle,
|
||||
pitch: ByteAngle,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UpdateEntityRotation {
|
||||
entity_id: VarInt,
|
||||
yaw: ByteAngle,
|
||||
pitch: ByteAngle,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
OpenScreen {
|
||||
window_id: VarInt,
|
||||
window_type: VarInt,
|
||||
window_title: Text,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PlayerChatMessage {
|
||||
// TODO: more 1.19 stuff.
|
||||
message: Text,
|
||||
/// Index into the chat type registry
|
||||
kind: VarInt,
|
||||
sender: Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
CombatDeath {
|
||||
player_id: VarInt,
|
||||
/// Killer's entity ID, -1 if no killer
|
||||
entity_id: i32,
|
||||
message: Text
|
||||
}
|
||||
}
|
||||
|
||||
def_enum! {
|
||||
PlayerInfo: VarInt {
|
||||
AddPlayer: Vec<PlayerListAddPlayer> = 0,
|
||||
UpdateGameMode: Vec<(Uuid, GameMode)> = 1,
|
||||
UpdateLatency: Vec<(Uuid, VarInt)> = 2,
|
||||
UpdateDisplayName: Vec<(Uuid, Option<Text>)> = 3,
|
||||
RemovePlayer: Vec<Uuid> = 4,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
PlayerListAddPlayer {
|
||||
uuid: Uuid,
|
||||
username: BoundedString<3, 16>,
|
||||
properties: Vec<Property>,
|
||||
game_mode: GameMode,
|
||||
ping: VarInt,
|
||||
display_name: Option<Text>,
|
||||
sig_data: Option<PublicKeyData>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SynchronizePlayerPosition {
|
||||
position: Vec3<f64>,
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
flags: PlayerPositionLookFlags,
|
||||
teleport_id: VarInt,
|
||||
dismount_vehicle: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_bitfield! {
|
||||
PlayerPositionLookFlags: u8 {
|
||||
x = 0,
|
||||
y = 1,
|
||||
z = 2,
|
||||
y_rot = 3,
|
||||
x_rot = 4,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
RemoveEntities {
|
||||
entities: Vec<VarInt>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
ResourcePackS2c {
|
||||
url: String,
|
||||
hash: BoundedString<0, 40>,
|
||||
forced: bool,
|
||||
prompt_message: Option<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
Respawn {
|
||||
dimension_type_name: Ident<String>,
|
||||
dimension_name: Ident<String>,
|
||||
hashed_seed: u64,
|
||||
game_mode: GameMode,
|
||||
previous_game_mode: GameMode,
|
||||
is_debug: bool,
|
||||
is_flat: bool,
|
||||
copy_metadata: bool,
|
||||
last_death_location: Option<(Ident<String>, BlockPos)>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetHeadRotation {
|
||||
entity_id: VarInt,
|
||||
head_yaw: ByteAngle,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UpdateSectionBlocks {
|
||||
chunk_section_position: i64,
|
||||
invert_trust_edges: bool,
|
||||
blocks: Vec<VarLong>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetActionBarText {
|
||||
text: Text
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetHeldItemS2c {
|
||||
slot: BoundedInt<u8, 0, 9>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetCenterChunk {
|
||||
chunk_x: VarInt,
|
||||
chunk_z: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetRenderDistance {
|
||||
view_distance: BoundedInt<VarInt, 2, 32>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetDefaultSpawnPosition {
|
||||
location: BlockPos,
|
||||
angle: f32,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetEntityMetadata {
|
||||
entity_id: VarInt,
|
||||
metadata: RawBytes,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetEntityVelocity {
|
||||
entity_id: VarInt,
|
||||
velocity: Vec3<i16>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetExperience {
|
||||
bar: f32,
|
||||
level: VarInt,
|
||||
total_xp: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetHealth {
|
||||
health: f32,
|
||||
food: VarInt,
|
||||
food_saturation: f32,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetSubtitleText {
|
||||
subtitle_text: Text,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UpdateTime {
|
||||
/// 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! {
|
||||
SetTitleText {
|
||||
text: Text,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
#[derive(Copy, PartialEq, Eq)]
|
||||
SetTitleAnimationTimes {
|
||||
/// Ticks to spend fading in.
|
||||
fade_in: u32,
|
||||
/// Ticks to keep the title displayed.
|
||||
stay: u32,
|
||||
/// Ticks to spend fading out.
|
||||
fade_out: u32,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EntitySoundEffect {
|
||||
id: VarInt,
|
||||
category: SoundCategory,
|
||||
entity_id: VarInt,
|
||||
volume: f32,
|
||||
pitch: f32
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SoundEffect {
|
||||
id: VarInt,
|
||||
category: SoundCategory,
|
||||
position: Vec3<i32>,
|
||||
volume: f32,
|
||||
pitch: f32,
|
||||
seed: i64
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SystemChatMessage {
|
||||
chat: Text,
|
||||
/// Index into the chat type registry.
|
||||
kind: VarInt,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
SetTabListHeaderAndFooter {
|
||||
header: Text,
|
||||
footer: Text,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
TeleportEntity {
|
||||
entity_id: VarInt,
|
||||
position: Vec3<f64>,
|
||||
yaw: ByteAngle,
|
||||
pitch: ByteAngle,
|
||||
on_ground: bool,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
UpdateAttributes {
|
||||
entity_id: VarInt,
|
||||
properties: Vec<EntityAttributesProperty>,
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EntityAttributesProperty {
|
||||
key: Ident<String>,
|
||||
value: f64,
|
||||
modifiers: Vec<EntityAttributesModifiers>
|
||||
}
|
||||
}
|
||||
|
||||
def_struct! {
|
||||
EntityAttributesModifiers {
|
||||
uuid: Uuid,
|
||||
amount: f64,
|
||||
operation: u8,
|
||||
}
|
||||
}
|
||||
|
||||
def_packet_group! {
|
||||
S2cPlayPacket {
|
||||
SpawnEntity = 0,
|
||||
SpawnExperienceOrb = 1,
|
||||
SpawnPlayer = 2,
|
||||
EntityAnimationS2c = 3,
|
||||
AcknowledgeBlockChange = 5,
|
||||
SetBlockDestroyStage = 6,
|
||||
BlockEntityData = 7,
|
||||
BlockAction = 8,
|
||||
BlockUpdate = 9,
|
||||
BossBar = 10,
|
||||
ClearTitles = 13,
|
||||
PluginMessageS2c = 22,
|
||||
SetContainerContent = 17,
|
||||
SetContainerProperty = 18,
|
||||
SetContainerSlot = 19,
|
||||
SetCooldown = 20,
|
||||
CustomSoundEffect = 23,
|
||||
DisconnectPlay = 25,
|
||||
EntityEvent = 26,
|
||||
UnloadChunk = 28,
|
||||
GameEvent = 29,
|
||||
KeepAliveS2c = 32,
|
||||
ChunkDataAndUpdateLight = 33,
|
||||
LoginPlay = 37,
|
||||
UpdateEntityPosition = 40,
|
||||
UpdateEntityPositionAndRotation = 41,
|
||||
UpdateEntityRotation = 42,
|
||||
OpenScreen = 45,
|
||||
PlayerChatMessage = 51,
|
||||
CombatDeath = 54,
|
||||
PlayerInfo = 55,
|
||||
SynchronizePlayerPosition = 57,
|
||||
RemoveEntities = 59,
|
||||
ResourcePackS2c = 61,
|
||||
Respawn = 62,
|
||||
SetHeadRotation = 63,
|
||||
UpdateSectionBlocks = 64,
|
||||
SetActionBarText = 67,
|
||||
SetHeldItemS2c = 74,
|
||||
SetCenterChunk = 75,
|
||||
SetRenderDistance = 76,
|
||||
SetDefaultSpawnPosition = 77,
|
||||
SetEntityMetadata = 80,
|
||||
SetEntityVelocity = 82,
|
||||
SetExperience = 84,
|
||||
SetHealth = 85,
|
||||
SetSubtitleText = 91,
|
||||
UpdateTime = 92,
|
||||
SetTitleText = 93,
|
||||
SetTitleAnimationTimes = 94,
|
||||
EntitySoundEffect = 95,
|
||||
SoundEffect = 96,
|
||||
SystemChatMessage = 98,
|
||||
SetTabListHeaderAndFooter = 99,
|
||||
TeleportEntity = 102,
|
||||
UpdateAttributes = 104,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
use std::io::Write;
|
||||
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
use crate::item::{ItemKind, ItemStack};
|
||||
use crate::nbt::Compound;
|
||||
use crate::protocol::{Decode, Encode};
|
||||
|
||||
pub type SlotId = i16;
|
||||
|
||||
/// Represents a slot in an inventory.
|
||||
pub type Slot = Option<ItemStack>;
|
||||
|
||||
impl Encode for Slot {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
match self {
|
||||
None => false.encode(w),
|
||||
Some(s) => {
|
||||
true.encode(w)?;
|
||||
s.item.encode(w)?;
|
||||
s.count().encode(w)?;
|
||||
match &s.nbt {
|
||||
Some(n) => n.encode(w),
|
||||
None => 0u8.encode(w),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
match self {
|
||||
None => 1,
|
||||
Some(s) => {
|
||||
1 + s.item.encoded_len()
|
||||
+ 1
|
||||
+ s.nbt.as_ref().map(|nbt| nbt.encoded_len()).unwrap_or(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Slot {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let present = bool::decode(r)?;
|
||||
if !present {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(ItemStack::new(
|
||||
ItemKind::decode(r)?,
|
||||
u8::decode(r)?,
|
||||
if r.first() == Some(&0) {
|
||||
r.read_u8()?;
|
||||
None
|
||||
} else {
|
||||
Some(Compound::decode(r)?)
|
||||
},
|
||||
)))
|
||||
}
|
||||
}
|
|
@ -22,6 +22,16 @@ use tokio::runtime::{Handle, Runtime};
|
|||
use tokio::sync::Semaphore;
|
||||
use uuid::Uuid;
|
||||
use valence_nbt::{compound, Compound, List};
|
||||
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use valence_protocol::packets::c2s::handshake::HandshakeOwned;
|
||||
use valence_protocol::packets::c2s::login::LoginStart;
|
||||
use valence_protocol::packets::c2s::status::{PingRequest, StatusRequest};
|
||||
use valence_protocol::packets::s2c::login::{DisconnectLogin, LoginSuccess, SetCompression};
|
||||
use valence_protocol::packets::s2c::status::{PingResponse, StatusResponse};
|
||||
use valence_protocol::types::HandshakeNextState;
|
||||
use valence_protocol::username::Username;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use valence_protocol::{ident, MINECRAFT_VERSION, PROTOCOL_VERSION};
|
||||
|
||||
use crate::biome::{validate_biomes, Biome, BiomeId};
|
||||
use crate::client::{Client, Clients};
|
||||
|
@ -31,17 +41,9 @@ use crate::entity::Entities;
|
|||
use crate::inventory::Inventories;
|
||||
use crate::player_list::PlayerLists;
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use crate::protocol::packets::c2s::handshake::{Handshake, HandshakeNextState};
|
||||
use crate::protocol::packets::c2s::login::LoginStart;
|
||||
use crate::protocol::packets::c2s::status::{PingRequest, StatusRequest};
|
||||
use crate::protocol::packets::s2c::login::{DisconnectLogin, LoginSuccess, SetCompression};
|
||||
use crate::protocol::packets::s2c::status::{PingResponse, StatusResponse};
|
||||
use crate::protocol::VarInt;
|
||||
use crate::server::packet_controller::InitialPacketController;
|
||||
use crate::username::Username;
|
||||
use crate::world::Worlds;
|
||||
use crate::{ident, Ticks, PROTOCOL_VERSION, VERSION_NAME};
|
||||
use crate::Ticks;
|
||||
|
||||
mod byte_channel;
|
||||
mod login;
|
||||
|
@ -502,7 +504,7 @@ async fn handle_connection(
|
|||
|
||||
// TODO: peek stream for 0xFE legacy ping
|
||||
|
||||
let handshake: Handshake = ctrl.recv_packet().await?;
|
||||
let handshake = ctrl.recv_packet::<HandshakeOwned>().await?;
|
||||
|
||||
ensure!(
|
||||
matches!(server.connection_mode(), ConnectionMode::BungeeCord)
|
||||
|
@ -540,7 +542,7 @@ async fn handle_status(
|
|||
server: SharedServer<impl Config>,
|
||||
mut ctrl: InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
|
||||
remote_addr: SocketAddr,
|
||||
handshake: Handshake,
|
||||
handshake: HandshakeOwned,
|
||||
) -> anyhow::Result<()> {
|
||||
ctrl.recv_packet::<StatusRequest>().await?;
|
||||
|
||||
|
@ -559,7 +561,7 @@ async fn handle_status(
|
|||
} => {
|
||||
let mut json = json!({
|
||||
"version": {
|
||||
"name": VERSION_NAME,
|
||||
"name": MINECRAFT_VERSION,
|
||||
"protocol": PROTOCOL_VERSION
|
||||
},
|
||||
"players": {
|
||||
|
@ -579,7 +581,7 @@ async fn handle_status(
|
|||
}
|
||||
|
||||
ctrl.send_packet(&StatusResponse {
|
||||
json_response: json.to_string(),
|
||||
json: &json.to_string(),
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
@ -598,7 +600,7 @@ async fn handle_login(
|
|||
server: &SharedServer<impl Config>,
|
||||
ctrl: &mut InitialPacketController<OwnedReadHalf, OwnedWriteHalf>,
|
||||
remote_addr: SocketAddr,
|
||||
handshake: Handshake,
|
||||
handshake: HandshakeOwned,
|
||||
) -> anyhow::Result<Option<NewClientData>> {
|
||||
if handshake.protocol_version.0 != PROTOCOL_VERSION {
|
||||
// TODO: send translated disconnect msg?
|
||||
|
@ -611,6 +613,8 @@ async fn handle_login(
|
|||
profile_id: _, // TODO
|
||||
} = ctrl.recv_packet().await?;
|
||||
|
||||
let username = username.to_owned_username();
|
||||
|
||||
let ncd = match server.connection_mode() {
|
||||
ConnectionMode::Online => login::online(server, ctrl, remote_addr, username).await?,
|
||||
ConnectionMode::Offline => login::offline(remote_addr, username)?,
|
||||
|
@ -635,7 +639,7 @@ async fn handle_login(
|
|||
|
||||
ctrl.send_packet(&LoginSuccess {
|
||||
uuid: ncd.uuid,
|
||||
username: ncd.username.clone(),
|
||||
username: ncd.username.as_str_username(),
|
||||
properties: Vec::new(),
|
||||
})
|
||||
.await?;
|
||||
|
|
|
@ -15,22 +15,22 @@ use sha1::Sha1;
|
|||
use sha2::{Digest, Sha256};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::ident;
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::protocol::packets::c2s::login::{
|
||||
EncryptionResponse, LoginPluginResponse, VerifyTokenOrMsgSig,
|
||||
};
|
||||
use crate::protocol::packets::s2c::login::{
|
||||
use valence_protocol::ident::Ident;
|
||||
use valence_protocol::packets::c2s::login::{EncryptionResponse, LoginPluginResponse};
|
||||
use valence_protocol::packets::s2c::login::{
|
||||
DisconnectLogin, EncryptionRequest, LoginPluginRequest,
|
||||
};
|
||||
use crate::protocol::packets::Property;
|
||||
use crate::protocol::{BoundedArray, Decode, RawBytes, VarInt};
|
||||
use valence_protocol::raw_bytes::RawBytes;
|
||||
use valence_protocol::text::Text;
|
||||
use valence_protocol::types::{MsgSigOrVerifyToken, SignedProperty, SignedPropertyOwned};
|
||||
use valence_protocol::username::Username;
|
||||
use valence_protocol::var_int::VarInt;
|
||||
use valence_protocol::Decode;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::player_textures::SignedPlayerTextures;
|
||||
use crate::server::packet_controller::InitialPacketController;
|
||||
use crate::server::{NewClientData, SharedServer};
|
||||
use crate::text::Text;
|
||||
use crate::username::Username;
|
||||
|
||||
/// Login sequence for
|
||||
/// [`ConnectionMode::Online`](crate::config::ConnectionMode).
|
||||
|
@ -43,38 +43,37 @@ pub(super) async fn online(
|
|||
let my_verify_token: [u8; 16] = rand::random();
|
||||
|
||||
ctrl.send_packet(&EncryptionRequest {
|
||||
server_id: Default::default(), // Always empty
|
||||
public_key: server.0.public_key_der.to_vec(),
|
||||
verify_token: my_verify_token.to_vec().into(),
|
||||
server_id: "", // Always empty
|
||||
public_key: &server.0.public_key_der,
|
||||
verify_token: &my_verify_token,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let EncryptionResponse {
|
||||
shared_secret: BoundedArray(encrypted_shared_secret),
|
||||
token_or_sig,
|
||||
shared_secret,
|
||||
sig_or_token,
|
||||
} = ctrl.recv_packet().await?;
|
||||
|
||||
let shared_secret = server
|
||||
.0
|
||||
.rsa_key
|
||||
.decrypt(PaddingScheme::PKCS1v15Encrypt, &encrypted_shared_secret)
|
||||
.decrypt(PaddingScheme::PKCS1v15Encrypt, shared_secret)
|
||||
.context("failed to decrypt shared secret")?;
|
||||
|
||||
let _opt_signature = match token_or_sig {
|
||||
VerifyTokenOrMsgSig::VerifyToken(BoundedArray(encrypted_verify_token)) => {
|
||||
match sig_or_token {
|
||||
MsgSigOrVerifyToken::VerifyToken(encrypted_verify_token) => {
|
||||
let verify_token = server
|
||||
.0
|
||||
.rsa_key
|
||||
.decrypt(PaddingScheme::PKCS1v15Encrypt, &encrypted_verify_token)
|
||||
.decrypt(PaddingScheme::PKCS1v15Encrypt, encrypted_verify_token)
|
||||
.context("failed to decrypt verify token")?;
|
||||
|
||||
ensure!(
|
||||
my_verify_token.as_slice() == verify_token,
|
||||
"verify tokens do not match"
|
||||
);
|
||||
None
|
||||
}
|
||||
VerifyTokenOrMsgSig::MsgSig(sig) => Some(sig),
|
||||
MsgSigOrVerifyToken::MsgSig { .. } => {}
|
||||
};
|
||||
|
||||
let crypt_key: [u8; 16] = shared_secret
|
||||
|
@ -84,13 +83,6 @@ pub(super) async fn online(
|
|||
|
||||
ctrl.enable_encryption(&crypt_key);
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AuthResponse {
|
||||
id: String,
|
||||
name: Username<String>,
|
||||
properties: Vec<Property>,
|
||||
}
|
||||
|
||||
let hash = Sha1::new()
|
||||
.chain(&shared_secret)
|
||||
.chain(&server.0.public_key_der)
|
||||
|
@ -117,6 +109,13 @@ pub(super) async fn online(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AuthResponse {
|
||||
id: String,
|
||||
name: Username<String>,
|
||||
properties: Vec<SignedPropertyOwned>,
|
||||
}
|
||||
|
||||
let data: AuthResponse = resp.json().await?;
|
||||
|
||||
ensure!(data.name == username, "usernames do not match");
|
||||
|
@ -169,7 +168,7 @@ pub(super) fn bungeecord(
|
|||
.map_err(|_| anyhow!("malformed BungeeCord server address data"))?;
|
||||
|
||||
// Read properties and get textures
|
||||
let properties: Vec<Property> =
|
||||
let properties: Vec<SignedProperty> =
|
||||
serde_json::from_str(properties).context("failed to parse BungeeCord player properties")?;
|
||||
|
||||
let mut textures = None;
|
||||
|
@ -212,8 +211,8 @@ pub(super) async fn velocity(
|
|||
// Send Player Info Request into the Plugin Channel
|
||||
ctrl.send_packet(&LoginPluginRequest {
|
||||
message_id: VarInt(message_id),
|
||||
channel: ident!("velocity:player_info"),
|
||||
data: RawBytes(vec![VELOCITY_MIN_SUPPORTED_VERSION]),
|
||||
channel: Ident::new("velocity:player_info").unwrap(),
|
||||
data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
@ -258,7 +257,7 @@ pub(super) async fn velocity(
|
|||
|
||||
// Read properties and get textures
|
||||
let mut textures = None;
|
||||
for prop in Vec::<Property>::decode(&mut data_without_signature)
|
||||
for prop in Vec::<SignedProperty>::decode(&mut data_without_signature)
|
||||
.context("failed to decode velocity player properties")?
|
||||
{
|
||||
if prop.name == "textures" {
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use std::io::ErrorKind;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::io;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::timeout;
|
||||
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use valence_protocol::{Decode, Encode, Packet};
|
||||
|
||||
use crate::protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
use crate::protocol::packets::{DecodePacket, EncodePacket};
|
||||
use crate::server::byte_channel::{byte_channel, ByteReceiver, ByteSender, TryRecvError};
|
||||
|
||||
pub struct InitialPacketController<R, W> {
|
||||
|
@ -42,9 +43,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn send_packet<P>(&mut self, pkt: &P) -> anyhow::Result<()>
|
||||
pub async fn send_packet<P>(&mut self, pkt: &P) -> Result<()>
|
||||
where
|
||||
P: EncodePacket + ?Sized,
|
||||
P: Encode + Packet + ?Sized,
|
||||
{
|
||||
self.enc.append_packet(pkt)?;
|
||||
let bytes = self.enc.take();
|
||||
|
@ -52,11 +53,32 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv_packet<P>(&mut self) -> anyhow::Result<P>
|
||||
pub async fn recv_packet<'a, P>(&'a mut self) -> Result<P>
|
||||
where
|
||||
P: DecodePacket,
|
||||
P: Decode<'a> + Packet,
|
||||
{
|
||||
timeout(self.timeout, async {
|
||||
while !self.dec.has_next_packet()? {
|
||||
self.dec.reserve(READ_BUF_SIZE);
|
||||
let mut buf = self.dec.take_capacity();
|
||||
|
||||
if self.reader.read_buf(&mut buf).await? == 0 {
|
||||
return Err(io::Error::from(ErrorKind::UnexpectedEof).into());
|
||||
}
|
||||
|
||||
// This should always be an O(1) unsplit because we reserved space earlier and
|
||||
// the call to `read_buf` shouldn't have grown the allocation.
|
||||
self.dec.queue_bytes(buf);
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.dec
|
||||
.try_next_packet()?
|
||||
.expect("decoder said it had another packet"))
|
||||
|
||||
// The following is what I want to write but can't due to borrow
|
||||
// checker errors I don't understand.
|
||||
/*
|
||||
loop {
|
||||
if let Some(pkt) = self.dec.try_next_packet()? {
|
||||
return Ok(pkt);
|
||||
|
@ -70,9 +92,10 @@ where
|
|||
}
|
||||
|
||||
// This should always be an O(1) unsplit because we reserved space earlier and
|
||||
// the previous call to `read_buf` shouldn't have grown the allocation.
|
||||
// the call to `read_buf` shouldn't have grown the allocation.
|
||||
self.dec.queue_bytes(buf);
|
||||
}
|
||||
*/
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
@ -165,23 +188,23 @@ pub struct PlayPacketController {
|
|||
}
|
||||
|
||||
impl PlayPacketController {
|
||||
pub fn append_packet<P>(&mut self, pkt: &P) -> anyhow::Result<()>
|
||||
pub fn append_packet<P>(&mut self, pkt: &P) -> Result<()>
|
||||
where
|
||||
P: EncodePacket + ?Sized,
|
||||
P: Encode + Packet + ?Sized,
|
||||
{
|
||||
self.enc.append_packet(pkt)
|
||||
}
|
||||
|
||||
pub fn prepend_packet<P>(&mut self, pkt: &P) -> anyhow::Result<()>
|
||||
pub fn prepend_packet<P>(&mut self, pkt: &P) -> Result<()>
|
||||
where
|
||||
P: EncodePacket + ?Sized,
|
||||
P: Encode + Packet + ?Sized,
|
||||
{
|
||||
self.enc.prepend_packet(pkt)
|
||||
}
|
||||
|
||||
pub fn try_next_packet<P>(&mut self) -> anyhow::Result<Option<P>>
|
||||
pub fn try_next_packet<'a, P>(&'a mut self) -> Result<Option<P>>
|
||||
where
|
||||
P: DecodePacket,
|
||||
P: Decode<'a> + Packet,
|
||||
{
|
||||
self.dec.try_next_packet()
|
||||
}
|
||||
|
@ -203,7 +226,7 @@ impl PlayPacketController {
|
|||
self.enc.set_compression(threshold)
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> anyhow::Result<()> {
|
||||
pub fn flush(&mut self) -> Result<()> {
|
||||
let bytes = self.enc.take();
|
||||
self.send.try_send(bytes)?;
|
||||
Ok(())
|
||||
|
|
13
valence_derive/Cargo.toml
Normal file
13
valence_derive/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "valence_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0.103", features = ["full"] }
|
||||
quote = "1.0.21"
|
||||
proc-macro2 = "1.0.47"
|
||||
|
218
valence_derive/src/decode.rs
Normal file
218
valence_derive/src/decode.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse2, parse_quote, Data, DeriveInput, Error, Fields, Result};
|
||||
|
||||
use crate::{
|
||||
add_trait_bounds, decode_split_for_impl, find_packet_id_attr, pair_variants_with_discriminants,
|
||||
};
|
||||
|
||||
pub fn derive_decode(item: TokenStream) -> Result<TokenStream> {
|
||||
let mut input = parse2::<DeriveInput>(item)?;
|
||||
|
||||
let name = input.ident;
|
||||
let string_name = name.to_string();
|
||||
|
||||
let packet_id = find_packet_id_attr(&input.attrs)?
|
||||
.into_iter()
|
||||
.map(|l| l.to_token_stream())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if input.generics.lifetimes().count() > 1 {
|
||||
return Err(Error::new(
|
||||
input.generics.params.span(),
|
||||
"type deriving `Decode` must have no more than one lifetime",
|
||||
));
|
||||
}
|
||||
|
||||
// Use the lifetime specified in the type definition or just use `'a` if not
|
||||
// present.
|
||||
let lifetime = input
|
||||
.generics
|
||||
.lifetimes()
|
||||
.next()
|
||||
.map(|l| l.lifetime.clone())
|
||||
.unwrap_or_else(|| parse_quote!('a));
|
||||
|
||||
match input.data {
|
||||
Data::Struct(struct_) => {
|
||||
let decode_fields = match struct_.fields {
|
||||
Fields::Named(fields) => {
|
||||
let init = fields.named.iter().map(|f| {
|
||||
let name = f.ident.as_ref().unwrap();
|
||||
let ctx = format!("failed to decode field `{name}`");
|
||||
quote! {
|
||||
#name: Decode::decode(_r).context(#ctx)?,
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
Self {
|
||||
#(#init)*
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let init = (0..fields.unnamed.len())
|
||||
.map(|i| {
|
||||
let ctx = format!("failed to decode field `{i}`");
|
||||
quote! {
|
||||
Decode::decode(_r).context(#ctx)?,
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
Self(#init)
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote!(Self),
|
||||
};
|
||||
|
||||
add_trait_bounds(
|
||||
&mut input.generics,
|
||||
quote!(::valence_protocol::Decode<#lifetime>),
|
||||
);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) =
|
||||
decode_split_for_impl(input.generics, lifetime.clone());
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::Decode<#lifetime> for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
||||
use ::valence_protocol::__private::{Decode, Context, VarInt, ensure};
|
||||
|
||||
#(
|
||||
let id = VarInt::decode(_r).context("failed to decode packet ID")?.0;
|
||||
ensure!(id == #packet_id, "unexpected packet ID {} (expected {})", id, #packet_id);
|
||||
)*
|
||||
|
||||
Ok(#decode_fields)
|
||||
}
|
||||
}
|
||||
|
||||
#(
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::DerivedPacketDecode<#lifetime> for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
const ID: i32 = #packet_id;
|
||||
const NAME: &'static str = #string_name;
|
||||
|
||||
fn decode_without_id(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
||||
use ::valence_protocol::__private::{Decode, Context, VarInt, ensure};
|
||||
|
||||
Ok(#decode_fields)
|
||||
}
|
||||
}
|
||||
)*
|
||||
})
|
||||
}
|
||||
Data::Enum(enum_) => {
|
||||
let variants = pair_variants_with_discriminants(enum_.variants.into_iter())?;
|
||||
|
||||
let decode_arms = variants
|
||||
.iter()
|
||||
.map(|(disc, variant)| {
|
||||
let name = &variant.ident;
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Named(fields) => {
|
||||
let fields = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let field = f.ident.as_ref().unwrap();
|
||||
let ctx = format!(
|
||||
"failed to decode field `{field}` in variant `{name}`",
|
||||
);
|
||||
quote! {
|
||||
#field: Decode::decode(_r).context(#ctx)?,
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
#disc => Ok(Self::#name { #fields }),
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let init = (0..fields.unnamed.len())
|
||||
.map(|i| {
|
||||
let ctx = format!(
|
||||
"failed to decode field `{i}` in variant `{name}`",
|
||||
);
|
||||
quote! {
|
||||
Decode::decode(_r).context(#ctx)?,
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
#disc => Ok(Self::#name(#init)),
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote!(#disc => Ok(Self::#name),),
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
add_trait_bounds(
|
||||
&mut input.generics,
|
||||
quote!(::valence_protocol::Decode<#lifetime>),
|
||||
);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) =
|
||||
decode_split_for_impl(input.generics, lifetime.clone());
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::Decode<#lifetime> for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
||||
use ::valence_protocol::__private::{Decode, Context, VarInt, bail, ensure};
|
||||
|
||||
#(
|
||||
let id = VarInt::decode(_r).context("failed to decode packet ID")?.0;
|
||||
ensure!(id == #packet_id, "unexpected packet ID {} (expected {})", id, #packet_id);
|
||||
)*
|
||||
|
||||
let disc = VarInt::decode(_r).context("failed to decode enum discriminant")?.0;
|
||||
match disc {
|
||||
#decode_arms
|
||||
n => bail!("unexpected enum discriminant {}", disc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#(
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::DerivedPacketDecode<#lifetime> for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
const ID: i32 = #packet_id;
|
||||
const NAME: &'static str = #string_name;
|
||||
|
||||
fn decode_without_id(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result<Self> {
|
||||
use ::valence_protocol::__private::{Decode, Context, VarInt, bail};
|
||||
|
||||
let disc = VarInt::decode(_r).context("failed to decode enum discriminant")?.0;
|
||||
match disc {
|
||||
#decode_arms
|
||||
n => bail!("unexpected enum discriminant {}", disc),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
})
|
||||
}
|
||||
Data::Union(u) => Err(Error::new(
|
||||
u.union_token.span(),
|
||||
"cannot derive `Decode` on unions",
|
||||
)),
|
||||
}
|
||||
}
|
315
valence_derive/src/encode.rs
Normal file
315
valence_derive/src/encode.rs
Normal file
|
@ -0,0 +1,315 @@
|
|||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{parse2, Data, DeriveInput, Error, Fields, LitInt, Result};
|
||||
|
||||
use crate::{add_trait_bounds, find_packet_id_attr, pair_variants_with_discriminants};
|
||||
|
||||
pub fn derive_encode(item: TokenStream) -> Result<TokenStream> {
|
||||
let mut input = parse2::<DeriveInput>(item)?;
|
||||
|
||||
let name = input.ident;
|
||||
let string_name = name.to_string();
|
||||
|
||||
let packet_id = find_packet_id_attr(&input.attrs)?
|
||||
.into_iter()
|
||||
.map(|l| l.to_token_stream())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match input.data {
|
||||
Data::Struct(struct_) => {
|
||||
add_trait_bounds(
|
||||
&mut input.generics,
|
||||
quote!(::valence_protocol::__private::Encode),
|
||||
);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let encode_fields = match &struct_.fields {
|
||||
Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let name = &f.ident.as_ref().unwrap();
|
||||
let ctx = format!("failed to encode field `{name}`");
|
||||
quote! {
|
||||
self.#name.encode(&mut _w).context(#ctx)?;
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
Fields::Unnamed(fields) => (0..fields.unnamed.len())
|
||||
.map(|i| {
|
||||
let lit = LitInt::new(&i.to_string(), Span::call_site());
|
||||
let ctx = format!("failed to encode field `{lit}`");
|
||||
quote! {
|
||||
self.#lit.encode(&mut _w).context(#ctx)?;
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
Fields::Unit => TokenStream::new(),
|
||||
};
|
||||
|
||||
let encoded_len_fields = match &struct_.fields {
|
||||
Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let name = &f.ident;
|
||||
quote! {
|
||||
+ self.#name.encoded_len()
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
Fields::Unnamed(fields) => (0..fields.unnamed.len())
|
||||
.map(|i| {
|
||||
let lit = LitInt::new(&i.to_string(), Span::call_site());
|
||||
quote! {
|
||||
+ self.#lit.encoded_len()
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
Fields::Unit => TokenStream::new(),
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::__private::Encode for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
||||
use ::valence_protocol::__private::{Encode, Context, VarInt};
|
||||
|
||||
#(
|
||||
VarInt(#packet_id)
|
||||
.encode(&mut _w)
|
||||
.context("failed to encode packet ID")?;
|
||||
)*
|
||||
|
||||
#encode_fields
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
use ::valence_protocol::__private::{Encode, Context, VarInt};
|
||||
|
||||
0 #(+ VarInt(#packet_id).encoded_len())* #encoded_len_fields
|
||||
}
|
||||
}
|
||||
|
||||
#(
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::__private::DerivedPacketEncode for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
const ID: i32 = #packet_id;
|
||||
const NAME: &'static str = #string_name;
|
||||
|
||||
fn encode_without_id(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
||||
use ::valence_protocol::__private::{Encode, Context, VarInt};
|
||||
|
||||
#encode_fields
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len_without_id(&self) -> usize {
|
||||
use ::valence_protocol::__private::{Encode, Context, VarInt};
|
||||
|
||||
0 #encoded_len_fields
|
||||
}
|
||||
}
|
||||
)*
|
||||
})
|
||||
}
|
||||
Data::Enum(enum_) => {
|
||||
add_trait_bounds(
|
||||
&mut input.generics,
|
||||
quote!(::valence_protocol::__private::Encode),
|
||||
);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let variants = pair_variants_with_discriminants(enum_.variants.into_iter())?;
|
||||
|
||||
let encode_arms = variants
|
||||
.iter()
|
||||
.map(|(disc, variant)| {
|
||||
let variant_name = &variant.ident;
|
||||
|
||||
let disc_ctx = format!(
|
||||
"failed to encode enum discriminant {disc} for variant `{variant_name}`",
|
||||
);
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Named(fields) => {
|
||||
let field_names = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| f.ident.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let encode_fields = field_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let ctx = format!(
|
||||
"failed to encode field `{name}` in variant \
|
||||
`{variant_name}`",
|
||||
);
|
||||
|
||||
quote! {
|
||||
#name.encode(&mut _w).context(#ctx)?;
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
Self::#variant_name { #(#field_names,)* } => {
|
||||
VarInt(#disc).encode(&mut _w).context(#disc_ctx)?;
|
||||
|
||||
#encode_fields
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let field_names = (0..fields.unnamed.len())
|
||||
.map(|i| Ident::new(&format!("_{i}"), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let encode_fields = field_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let ctx = format!(
|
||||
"failed to encode field `{name}` in variant \
|
||||
`{variant_name}`"
|
||||
);
|
||||
|
||||
quote! {
|
||||
#name.encode(&mut _w).context(#ctx)?;
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
Self::#variant_name(#(#field_names,)*) => {
|
||||
VarInt(#disc).encode(&mut _w).context(#disc_ctx)?;
|
||||
|
||||
#encode_fields
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote! {
|
||||
Self::#variant_name => Ok(
|
||||
VarInt(#disc)
|
||||
.encode(&mut _w)
|
||||
.context(#disc_ctx)?
|
||||
),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
let encoded_len_arms = variants
|
||||
.iter()
|
||||
.map(|(disc, variant)| {
|
||||
let name = &variant.ident;
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Named(fields) => {
|
||||
let fields = fields.named.iter().map(|f| &f.ident).collect::<Vec<_>>();
|
||||
|
||||
quote! {
|
||||
Self::#name { #(#fields,)* } => {
|
||||
VarInt(#disc).encoded_len()
|
||||
|
||||
#(+ #fields.encoded_len())*
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let fields = (0..fields.unnamed.len())
|
||||
.map(|i| Ident::new(&format!("_{i}"), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
quote! {
|
||||
Self::#name(#(#fields,)*) => {
|
||||
VarInt(#disc).encoded_len()
|
||||
|
||||
#(+ #fields.encoded_len())*
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unit => {
|
||||
quote! {
|
||||
Self::#name => VarInt(#disc).encoded_len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(unused_imports, unreachable_code)]
|
||||
impl #impl_generics ::valence_protocol::Encode for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
||||
use ::valence_protocol::__private::{Encode, VarInt, Context};
|
||||
|
||||
#(
|
||||
VarInt(#packet_id)
|
||||
.encode(&mut _w)
|
||||
.context("failed to encode packet ID")?;
|
||||
)*
|
||||
|
||||
match self {
|
||||
#encode_arms
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
fn encoded_len(&self) -> usize {
|
||||
use ::valence_protocol::__private::{Encode, Context, VarInt};
|
||||
|
||||
#(VarInt(#packet_id).encoded_len() +)* match self {
|
||||
#encoded_len_arms
|
||||
_ => unreachable!() as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#(
|
||||
#[allow(unused_imports)]
|
||||
impl #impl_generics ::valence_protocol::DerivedPacketEncode for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
const ID: i32 = #packet_id;
|
||||
const NAME: &'static str = #string_name;
|
||||
|
||||
fn encode_without_id(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> {
|
||||
use ::valence_protocol::__private::{Encode, VarInt, Context};
|
||||
|
||||
match self {
|
||||
#encode_arms
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
fn encoded_len_without_id(&self) -> usize {
|
||||
use ::valence_protocol::__private::{Encode, Context, VarInt};
|
||||
|
||||
match self {
|
||||
#encoded_len_arms
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
})
|
||||
}
|
||||
Data::Union(u) => Err(Error::new(
|
||||
u.union_token.span(),
|
||||
"cannot derive `Encode` on unions",
|
||||
)),
|
||||
}
|
||||
}
|
151
valence_derive/src/lib.rs
Normal file
151
valence_derive/src/lib.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
//! This crate provides derive macros for [`Encode`], [`Decode`], and
|
||||
//! [`Packet`].
|
||||
//!
|
||||
//! See `valence_protocol`'s documentation for more information.
|
||||
|
||||
use proc_macro::TokenStream as StdTokenStream;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse2, parse_quote, Attribute, DeriveInput, Error, GenericParam, Generics, Lifetime,
|
||||
LifetimeDef, Lit, LitInt, Meta, Result, Variant,
|
||||
};
|
||||
|
||||
mod decode;
|
||||
mod encode;
|
||||
|
||||
#[proc_macro_derive(Encode, attributes(packet_id, tag))]
|
||||
pub fn derive_encode(item: StdTokenStream) -> StdTokenStream {
|
||||
match encode::derive_encode(item.into()) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Decode, attributes(packet_id, tag))]
|
||||
pub fn derive_decode(item: StdTokenStream) -> StdTokenStream {
|
||||
match decode::derive_decode(item.into()) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Packet)]
|
||||
pub fn derive_packet(item: StdTokenStream) -> StdTokenStream {
|
||||
match derive_packet_inner(item.into()) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_packet_inner(item: TokenStream) -> Result<TokenStream> {
|
||||
let input = parse2::<DeriveInput>(item)?;
|
||||
|
||||
if find_packet_id_attr(&input.attrs)?.is_none() {
|
||||
return Err(Error::new(
|
||||
Span::call_site(),
|
||||
"cannot derive `Packet` without `#[packet_id = ...]` attribute. Consider implementing \
|
||||
the trait manually",
|
||||
));
|
||||
}
|
||||
|
||||
let name = input.ident;
|
||||
let string_name = name.to_string();
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics ::valence_protocol::Packet for #name #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn packet_name(&self) -> &'static str {
|
||||
#string_name
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn find_packet_id_attr(attrs: &[Attribute]) -> Result<Option<LitInt>> {
|
||||
for attr in attrs {
|
||||
if let Meta::NameValue(nv) = attr.parse_meta()? {
|
||||
if nv.path.is_ident("packet_id") {
|
||||
let span = nv.lit.span();
|
||||
return match nv.lit {
|
||||
Lit::Int(i) => Ok(Some(i)),
|
||||
_ => Err(Error::new(span, "packet ID must be an integer literal")),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn pair_variants_with_discriminants(
|
||||
variants: impl IntoIterator<Item = Variant>,
|
||||
) -> Result<Vec<(i32, Variant)>> {
|
||||
let mut discriminant = 0;
|
||||
variants
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
if let Some(i) = find_tag_attr(&v.attrs)? {
|
||||
discriminant = i;
|
||||
}
|
||||
|
||||
let pair = (discriminant, v);
|
||||
discriminant += 1;
|
||||
Ok(pair)
|
||||
})
|
||||
.collect::<Result<_>>()
|
||||
}
|
||||
|
||||
fn find_tag_attr(attrs: &[Attribute]) -> Result<Option<i32>> {
|
||||
for attr in attrs {
|
||||
if let Meta::NameValue(nv) = attr.parse_meta()? {
|
||||
if nv.path.is_ident("tag") {
|
||||
let span = nv.lit.span();
|
||||
return match nv.lit {
|
||||
Lit::Int(lit) => Ok(Some(lit.base10_parse::<i32>()?)),
|
||||
_ => Err(Error::new(
|
||||
span,
|
||||
"discriminant value must be an integer literal",
|
||||
)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Adding our lifetime to the generics before calling `.split_for_impl()` would
|
||||
/// also add it to the resulting ty_generics, which we don't want. So I'm doing
|
||||
/// this hack.
|
||||
fn decode_split_for_impl(
|
||||
mut generics: Generics,
|
||||
lifetime: Lifetime,
|
||||
) -> (TokenStream, TokenStream, TokenStream) {
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let mut impl_generics = impl_generics.to_token_stream();
|
||||
let ty_generics = ty_generics.to_token_stream();
|
||||
let where_clause = where_clause.to_token_stream();
|
||||
|
||||
if generics.lifetimes().next().is_none() {
|
||||
generics
|
||||
.params
|
||||
.push(GenericParam::Lifetime(LifetimeDef::new(lifetime)));
|
||||
|
||||
impl_generics = generics.split_for_impl().0.to_token_stream();
|
||||
}
|
||||
|
||||
(impl_generics, ty_generics, where_clause)
|
||||
}
|
||||
|
||||
fn add_trait_bounds(generics: &mut Generics, trait_: TokenStream) {
|
||||
for param in &mut generics.params {
|
||||
if let GenericParam::Type(type_param) = param {
|
||||
type_param.bounds.push(parse_quote!(#trait_))
|
||||
}
|
||||
}
|
||||
}
|
36
valence_protocol/Cargo.toml
Normal file
36
valence_protocol/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
|||
[package]
|
||||
name = "valence_protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build/main.rs"
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", optional = true }
|
||||
anyhow = "1.0.66"
|
||||
arrayvec = "0.7.2"
|
||||
bitfield-struct = "0.1.7"
|
||||
byteorder = "1.4.3"
|
||||
bytes = "1.2.1"
|
||||
cfb8 = { version = "0.7.1", optional = true }
|
||||
flate2 = { version = "1.0.24", optional = true }
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_json = "1.0.87"
|
||||
thiserror = "1.0.37"
|
||||
uuid = "1.2.1"
|
||||
valence_derive = { version = "0.1.0", path = "../valence_derive" }
|
||||
valence_nbt = { version = "0.4.0", path = "../valence_nbt" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.65"
|
||||
heck = "0.4.0"
|
||||
proc-macro2 = "1.0.43"
|
||||
quote = "1.0.21"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde_json = "1.0.85"
|
||||
|
||||
[features]
|
||||
encryption = ["dep:aes", "dep:cfb8"]
|
||||
compression = ["dep:flate2"]
|
|
@ -62,7 +62,7 @@ struct Shape {
|
|||
|
||||
pub fn build() -> anyhow::Result<TokenStream> {
|
||||
let TopLevel { blocks, shapes } =
|
||||
serde_json::from_str(include_str!("../extracted/blocks.json"))?;
|
||||
serde_json::from_str(include_str!("../../extracted/blocks.json"))?;
|
||||
|
||||
let max_state_id = blocks.iter().map(|b| b.max_state_id()).max().unwrap();
|
||||
|
||||
|
@ -82,7 +82,7 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
|||
.map(|b| {
|
||||
let min = b.min_state_id();
|
||||
let max = b.max_state_id();
|
||||
let name = ident(&b.name.to_pascal_case());
|
||||
let name = ident(b.name.to_pascal_case());
|
||||
quote! {
|
||||
#min..=#max => BlockKind::#name,
|
||||
}
|
||||
|
@ -541,19 +541,13 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
|||
#(#shapes,)*
|
||||
];
|
||||
|
||||
pub fn collision_shapes(self) -> impl ExactSizeIterator<Item = vek::Aabb<f64>> + FusedIterator + Clone {
|
||||
pub fn collision_shapes(self) -> impl ExactSizeIterator<Item = [f64; 6]> + FusedIterator + Clone {
|
||||
let shape_idxs: &'static [u16] = match self.0 {
|
||||
#state_to_collision_shapes_arms
|
||||
_ => &[],
|
||||
};
|
||||
|
||||
shape_idxs.iter().map(|idx| {
|
||||
let [min_x, min_y, min_z, max_x, max_y, max_z] = Self::SHAPES[*idx as usize];
|
||||
vek::Aabb {
|
||||
min: vek::Vec3::new(min_x, min_y, min_z),
|
||||
max: vek::Vec3::new(max_x, max_y, max_z),
|
||||
}
|
||||
})
|
||||
shape_idxs.into_iter().map(|idx| Self::SHAPES[*idx as usize])
|
||||
}
|
||||
|
||||
pub const fn luminance(self) -> u8 {
|
|
@ -29,7 +29,7 @@ pub struct EnchantmentSources {
|
|||
|
||||
pub fn build() -> anyhow::Result<TokenStream> {
|
||||
let enchants: Vec<Enchantment> =
|
||||
serde_json::from_str(include_str!("../extracted/enchants.json"))?;
|
||||
serde_json::from_str(include_str!("../../extracted/enchants.json"))?;
|
||||
|
||||
let enchantmentkind_definitions = enchants
|
||||
.iter()
|
|
@ -29,7 +29,7 @@ struct FoodComponent {
|
|||
}
|
||||
|
||||
pub fn build() -> anyhow::Result<TokenStream> {
|
||||
let items = serde_json::from_str::<Vec<Item>>(include_str!("../extracted/items.json"))?;
|
||||
let items = serde_json::from_str::<Vec<Item>>(include_str!("../../extracted/items.json"))?;
|
||||
|
||||
let item_kind_count = items.len();
|
||||
|
44
valence_protocol/build/main.rs
Normal file
44
valence_protocol/build/main.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::{env, fs};
|
||||
|
||||
use anyhow::Context;
|
||||
use proc_macro2::{Ident, Span};
|
||||
|
||||
mod block;
|
||||
mod enchant;
|
||||
mod item;
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
println!("cargo:rerun-if-changed=extracted/");
|
||||
|
||||
let generators = [
|
||||
(block::build as fn() -> _, "block.rs"),
|
||||
(item::build, "item.rs"),
|
||||
(enchant::build, "enchant.rs"),
|
||||
];
|
||||
|
||||
let out_dir = env::var_os("OUT_DIR").context("failed to get OUT_DIR env var")?;
|
||||
|
||||
for (gen, file_name) in generators {
|
||||
let path = Path::new(&out_dir).join(file_name);
|
||||
let code = gen()?.to_string();
|
||||
fs::write(&path, code)?;
|
||||
|
||||
// Format the output for debugging purposes.
|
||||
// Doesn't matter if rustfmt is unavailable.
|
||||
let _ = Command::new("rustfmt").arg(path).output();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ident(s: impl AsRef<str>) -> Ident {
|
||||
let s = s.as_ref().trim();
|
||||
|
||||
match s.as_bytes() {
|
||||
// TODO: check for the other rust keywords.
|
||||
[b'0'..=b'9', ..] | b"type" => Ident::new(&format!("_{s}"), Span::call_site()),
|
||||
_ => Ident::new(s, Span::call_site()),
|
||||
}
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
//! Blocks and related types.
|
||||
#![allow(clippy::all)] // TODO: block build script creates many warnings.
|
||||
|
||||
#![allow(clippy::all, missing_docs)]
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::io::Write;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
pub use crate::block_pos::BlockPos;
|
||||
use crate::item::ItemKind;
|
||||
use crate::protocol::{Decode, Encode, VarInt};
|
||||
use crate::var_int::VarInt;
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/block.rs"));
|
||||
|
||||
|
@ -53,7 +52,7 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
}
|
||||
|
||||
impl Encode for BlockState {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
VarInt(self.0 as i32).encode(w)
|
||||
}
|
||||
|
||||
|
@ -62,8 +61,8 @@ impl Encode for BlockState {
|
|||
}
|
||||
}
|
||||
|
||||
impl Decode for BlockState {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
impl Decode<'_> for BlockState {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let id = VarInt::decode(r)?.0;
|
||||
let errmsg = "invalid block state ID";
|
||||
|
||||
|
@ -71,6 +70,22 @@ impl Decode for BlockState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum BlockFace {
|
||||
/// -Y
|
||||
Bottom,
|
||||
/// +Y
|
||||
Top,
|
||||
/// -Z
|
||||
North,
|
||||
/// +Z
|
||||
South,
|
||||
/// -X
|
||||
West,
|
||||
/// +X
|
||||
East,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
|
@ -1,10 +1,9 @@
|
|||
use std::io::Write;
|
||||
|
||||
use anyhow::bail;
|
||||
use vek::Vec3;
|
||||
|
||||
use crate::client::BlockFace;
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::block::BlockFace;
|
||||
use crate::{Decode, Encode};
|
||||
|
||||
/// Represents an absolute block position in world space.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
|
||||
|
@ -21,16 +20,16 @@ impl BlockPos {
|
|||
}
|
||||
|
||||
/// Returns the block position a point is contained within.
|
||||
pub fn at(pos: impl Into<Vec3<f64>>) -> Self {
|
||||
pos.into().floor().as_::<i32>().into()
|
||||
pub fn at(pos: impl Into<[f64; 3]>) -> Self {
|
||||
pos.into().map(|a| a.floor() as i32).into()
|
||||
}
|
||||
|
||||
/// Get a new [`BlockPos`] that is adjacent to this position in `dir`
|
||||
/// direction.
|
||||
///
|
||||
/// ```rust
|
||||
/// use valence::block::BlockPos;
|
||||
/// use valence::client::BlockFace;
|
||||
/// ```
|
||||
/// use valence_protocol::block::BlockFace;
|
||||
/// use valence_protocol::block_pos::BlockPos;
|
||||
///
|
||||
/// let pos = BlockPos::new(0, 0, 0);
|
||||
/// let adj = pos.get_in_direction(BlockFace::South);
|
||||
|
@ -49,7 +48,7 @@ impl BlockPos {
|
|||
}
|
||||
|
||||
impl Encode for BlockPos {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, w: impl Write) -> anyhow::Result<()> {
|
||||
match (self.x, self.y, self.z) {
|
||||
(-0x2000000..=0x1ffffff, -0x800..=0x7ff, -0x2000000..=0x1ffffff) => {
|
||||
let (x, y, z) = (self.x as u64, self.y as u64, self.z as u64);
|
||||
|
@ -64,7 +63,7 @@ impl Encode for BlockPos {
|
|||
}
|
||||
}
|
||||
|
||||
impl Decode for BlockPos {
|
||||
impl Decode<'_> for BlockPos {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
// Use arithmetic right shift to determine sign.
|
||||
let val = i64::decode(r)?;
|
||||
|
@ -103,18 +102,6 @@ impl From<BlockPos> for [i32; 3] {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vec3<i32>> for BlockPos {
|
||||
fn from(pos: Vec3<i32>) -> Self {
|
||||
Self::new(pos.x, pos.y, pos.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockPos> for Vec3<i32> {
|
||||
fn from(pos: BlockPos) -> Self {
|
||||
Vec3::new(pos.x, pos.y, pos.z)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
108
valence_protocol/src/bounded.rs
Normal file
108
valence_protocol/src/bounded.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
// TODO: implement BoundedFloat when floats are permitted in const generics.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::ensure;
|
||||
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
/// An integer with a minimum and maximum value known at compile time. `T` is
|
||||
/// the underlying integer type.
|
||||
///
|
||||
/// If the value is not in bounds, an error is generated while
|
||||
/// encoding or decoding.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct BoundedInt<T, const MIN: i128, const MAX: i128>(pub T);
|
||||
|
||||
impl<T, const MIN: i128, const MAX: i128> Encode for BoundedInt<T, MIN, MAX>
|
||||
where
|
||||
T: Encode + Clone + Into<i128>,
|
||||
{
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
let n = self.0.clone().into();
|
||||
|
||||
ensure!(
|
||||
(MIN..=MAX).contains(&n),
|
||||
"integer is not in bounds while encoding (got {n}, expected {MIN}..={MAX})"
|
||||
);
|
||||
|
||||
self.0.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, const MIN: i128, const MAX: i128> Decode<'a> for BoundedInt<T, MIN, MAX>
|
||||
where
|
||||
T: Decode<'a> + Clone + Into<i128>,
|
||||
{
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
let res = T::decode(r)?;
|
||||
let n = res.clone().into();
|
||||
|
||||
ensure!(
|
||||
(MIN..=MAX).contains(&n),
|
||||
"integer is not in bounds while decoding (got {n}, expected {MIN}..={MAX})"
|
||||
);
|
||||
|
||||
Ok(Self(res))
|
||||
}
|
||||
}
|
||||
|
||||
/// A string with a minimum and maximum character length known at compile time.
|
||||
/// `S` is the underlying string type which is anything that implements
|
||||
/// `AsRef<str>`.
|
||||
///
|
||||
/// If the string is not in bounds, an error is generated while
|
||||
/// encoding or decoding.
|
||||
///
|
||||
/// Note that the length is a count of the _characters_ in the string, not
|
||||
/// bytes.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct BoundedString<S, const MIN: usize, const MAX: usize>(pub S);
|
||||
|
||||
impl<S, const MIN: usize, const MAX: usize> Encode for BoundedString<S, MIN, MAX>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
let s = self.0.as_ref();
|
||||
let cnt = s.chars().count();
|
||||
|
||||
ensure!(
|
||||
(MIN..=MAX).contains(&s.chars().count()),
|
||||
"char count of string is out of bounds while encoding (got {cnt}, expected \
|
||||
{MIN}..={MAX})"
|
||||
);
|
||||
|
||||
s.encode(w)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, const MIN: usize, const MAX: usize> Decode<'a> for BoundedString<S, MIN, MAX>
|
||||
where
|
||||
S: Decode<'a> + AsRef<str>,
|
||||
{
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
let s = S::decode(r)?;
|
||||
let cnt = s.as_ref().chars().count();
|
||||
|
||||
ensure!(
|
||||
(MIN..=MAX).contains(&cnt),
|
||||
"char count of string is out of bounds while decoding (got {cnt}, expected \
|
||||
{MIN}..={MAX})"
|
||||
);
|
||||
|
||||
Ok(Self(s))
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,7 +1,7 @@
|
|||
use std::f32::consts::TAU;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
/// Represents an angle in steps of 1/256 of a full turn.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
|
@ -26,7 +26,7 @@ impl ByteAngle {
|
|||
}
|
||||
|
||||
impl Encode for ByteAngle {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.0.encode(w)
|
||||
}
|
||||
|
||||
|
@ -35,8 +35,8 @@ impl Encode for ByteAngle {
|
|||
}
|
||||
}
|
||||
|
||||
impl Decode for ByteAngle {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
impl Decode<'_> for ByteAngle {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
u8::decode(r).map(ByteAngle)
|
||||
}
|
||||
}
|
26
valence_protocol/src/byte_counter.rs
Normal file
26
valence_protocol/src/byte_counter.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use std::io;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||
pub struct ByteCounter(pub usize);
|
||||
|
||||
impl ByteCounter {
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for ByteCounter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0 += buf.len();
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
|
||||
self.0 += buf.len();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,28 +1,24 @@
|
|||
use std::io::Read;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
use aes::cipher::{AsyncStreamCipher, NewCipher};
|
||||
use aes::Aes128;
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use anyhow::{bail, ensure};
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use cfb8::Cfb8;
|
||||
use flate2::bufread::ZlibDecoder;
|
||||
use flate2::write::ZlibEncoder;
|
||||
use flate2::Compression;
|
||||
use log::log_enabled;
|
||||
|
||||
use crate::protocol::packets::{DecodePacket, EncodePacket};
|
||||
use crate::protocol::var_int::VarIntDecodeError;
|
||||
use crate::protocol::{Decode, Encode, VarInt, MAX_PACKET_SIZE};
|
||||
use crate::var_int::{VarInt, VarIntDecodeError};
|
||||
use crate::{Decode, Encode, Packet, Result, MAX_PACKET_SIZE};
|
||||
|
||||
/// The AES block cipher with a 128 bit key, using the CFB-8 mode of
|
||||
/// operation.
|
||||
type Cipher = Cfb8<Aes128>;
|
||||
#[cfg(feature = "encryption")]
|
||||
type Cipher = cfb8::Cfb8<aes::Aes128>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PacketEncoder {
|
||||
buf: BytesMut,
|
||||
#[cfg(feature = "compression")]
|
||||
compress_buf: Vec<u8>,
|
||||
#[cfg(feature = "compression")]
|
||||
compression_threshold: Option<u32>,
|
||||
#[cfg(feature = "encryption")]
|
||||
cipher: Option<Cipher>,
|
||||
}
|
||||
|
||||
|
@ -31,30 +27,34 @@ impl PacketEncoder {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub fn append_packet<P>(&mut self, pkt: &P) -> anyhow::Result<()>
|
||||
pub fn append_packet<P>(&mut self, pkt: &P) -> Result<()>
|
||||
where
|
||||
P: EncodePacket + ?Sized,
|
||||
P: Encode + Packet + ?Sized,
|
||||
{
|
||||
self.append_or_prepend_packet::<true>(pkt)
|
||||
}
|
||||
|
||||
pub fn prepend_packet<P>(&mut self, pkt: &P) -> anyhow::Result<()>
|
||||
pub fn prepend_packet<P>(&mut self, pkt: &P) -> Result<()>
|
||||
where
|
||||
P: EncodePacket + ?Sized,
|
||||
P: Encode + Packet + ?Sized,
|
||||
{
|
||||
self.append_or_prepend_packet::<false>(pkt)
|
||||
}
|
||||
|
||||
fn append_or_prepend_packet<const APPEND: bool>(
|
||||
&mut self,
|
||||
pkt: &(impl EncodePacket + ?Sized),
|
||||
) -> anyhow::Result<()> {
|
||||
let data_len = pkt.encoded_packet_len();
|
||||
pkt: &(impl Encode + Packet + ?Sized),
|
||||
) -> Result<()> {
|
||||
let data_len = pkt.encoded_len();
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
if let Some(threshold) = self.compression_threshold {
|
||||
use flate2::write::ZlibEncoder;
|
||||
use flate2::Compression;
|
||||
|
||||
if data_len >= threshold as usize {
|
||||
let mut z = ZlibEncoder::new(&mut self.compress_buf, Compression::new(4));
|
||||
pkt.encode_packet(&mut z)?;
|
||||
pkt.encode(&mut z)?;
|
||||
drop(z);
|
||||
|
||||
let packet_len = VarInt(data_len as i32).encoded_len() + self.compress_buf.len();
|
||||
|
@ -96,7 +96,7 @@ impl PacketEncoder {
|
|||
if APPEND {
|
||||
VarInt(packet_len as i32).encode(&mut writer)?;
|
||||
VarInt(0).encode(&mut writer)?; // 0 for no compression on this packet.
|
||||
pkt.encode_packet(&mut writer)?;
|
||||
pkt.encode(&mut writer)?;
|
||||
} else {
|
||||
let mut slice = move_forward_by(
|
||||
&mut self.buf,
|
||||
|
@ -105,7 +105,7 @@ impl PacketEncoder {
|
|||
|
||||
VarInt(packet_len as i32).encode(&mut slice)?;
|
||||
VarInt(0).encode(&mut slice)?;
|
||||
pkt.encode_packet(&mut slice)?;
|
||||
pkt.encode(&mut slice)?;
|
||||
|
||||
debug_assert!(
|
||||
slice.is_empty(),
|
||||
|
@ -117,36 +117,37 @@ impl PacketEncoder {
|
|||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let packet_len = data_len;
|
||||
|
||||
ensure!(
|
||||
packet_len <= MAX_PACKET_SIZE as usize,
|
||||
"packet exceeds maximum length"
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let packet_len = data_len;
|
||||
|
||||
ensure!(
|
||||
packet_len <= MAX_PACKET_SIZE as usize,
|
||||
"packet exceeds maximum length"
|
||||
);
|
||||
|
||||
if APPEND {
|
||||
let mut writer = (&mut self.buf).writer();
|
||||
VarInt(packet_len as i32).encode(&mut writer)?;
|
||||
pkt.encode(&mut writer)?;
|
||||
} else {
|
||||
let mut slice = move_forward_by(
|
||||
&mut self.buf,
|
||||
VarInt(packet_len as i32).encoded_len() + packet_len,
|
||||
);
|
||||
|
||||
if APPEND {
|
||||
let mut writer = (&mut self.buf).writer();
|
||||
VarInt(packet_len as i32).encode(&mut writer)?;
|
||||
pkt.encode_packet(&mut writer)?;
|
||||
} else {
|
||||
let mut slice = move_forward_by(
|
||||
&mut self.buf,
|
||||
VarInt(packet_len as i32).encoded_len() + packet_len,
|
||||
);
|
||||
VarInt(packet_len as i32).encode(&mut slice)?;
|
||||
pkt.encode(&mut slice)?;
|
||||
|
||||
VarInt(packet_len as i32).encode(&mut slice)?;
|
||||
pkt.encode_packet(&mut slice)?;
|
||||
|
||||
debug_assert!(
|
||||
slice.is_empty(),
|
||||
"actual size of {} packet differs from reported size (actual = {}, reported = \
|
||||
{})",
|
||||
pkt.packet_name(),
|
||||
data_len - slice.len(),
|
||||
data_len,
|
||||
);
|
||||
}
|
||||
debug_assert!(
|
||||
slice.is_empty(),
|
||||
"actual size of {} packet differs from reported size (actual = {}, reported = {})",
|
||||
pkt.packet_name(),
|
||||
data_len - slice.len(),
|
||||
data_len,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -155,6 +156,7 @@ impl PacketEncoder {
|
|||
/// Takes all the packets written so far and encrypts them if encryption is
|
||||
/// enabled.
|
||||
pub fn take(&mut self) -> BytesMut {
|
||||
#[cfg(feature = "encryption")]
|
||||
if let Some(cipher) = &mut self.cipher {
|
||||
cipher.encrypt(&mut self.buf);
|
||||
}
|
||||
|
@ -162,14 +164,16 @@ impl PacketEncoder {
|
|||
self.buf.split()
|
||||
}
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
pub fn set_compression(&mut self, threshold: Option<u32>) {
|
||||
self.compression_threshold = threshold;
|
||||
}
|
||||
|
||||
/// Enables encryption for all future packets **and any packets that have
|
||||
/// Encrypts all future packets **and any packets that have
|
||||
/// not been [taken] yet.**
|
||||
///
|
||||
/// [taken]: Self::take
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn enable_encryption(&mut self, key: &[u8; 16]) {
|
||||
assert!(self.cipher.is_none(), "encryption is already enabled");
|
||||
self.cipher = Some(NewCipher::new(key.into(), key.into()));
|
||||
|
@ -188,8 +192,12 @@ fn move_forward_by(bytes: &mut BytesMut, count: usize) -> &mut [u8] {
|
|||
#[derive(Default)]
|
||||
pub struct PacketDecoder {
|
||||
buf: BytesMut,
|
||||
cursor: usize,
|
||||
#[cfg(feature = "compression")]
|
||||
decompress_buf: Vec<u8>,
|
||||
compression: bool,
|
||||
#[cfg(feature = "compression")]
|
||||
compression_enabled: bool,
|
||||
#[cfg(feature = "encryption")]
|
||||
cipher: Option<Cipher>,
|
||||
}
|
||||
|
||||
|
@ -198,10 +206,13 @@ impl PacketDecoder {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub fn try_next_packet<P>(&mut self) -> anyhow::Result<Option<P>>
|
||||
pub fn try_next_packet<'a, P>(&'a mut self) -> Result<Option<P>>
|
||||
where
|
||||
P: DecodePacket,
|
||||
P: Decode<'a> + Packet,
|
||||
{
|
||||
self.buf.advance(self.cursor);
|
||||
self.cursor = 0;
|
||||
|
||||
let mut r = &self.buf[..];
|
||||
|
||||
let packet_len = match VarInt::decode_partial(&mut r) {
|
||||
|
@ -221,7 +232,8 @@ impl PacketDecoder {
|
|||
|
||||
r = &r[..packet_len as usize];
|
||||
|
||||
let packet = if self.compression {
|
||||
#[cfg(feature = "compression")]
|
||||
let packet = if self.compression_enabled {
|
||||
let data_len = VarInt::decode(&mut r)?.0;
|
||||
|
||||
ensure!(
|
||||
|
@ -230,6 +242,11 @@ impl PacketDecoder {
|
|||
);
|
||||
|
||||
if data_len != 0 {
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::Context;
|
||||
use flate2::bufread::ZlibDecoder;
|
||||
|
||||
self.decompress_buf.clear();
|
||||
self.decompress_buf.reserve_exact(data_len as usize);
|
||||
let mut z = ZlibDecoder::new(r).take(data_len as u64);
|
||||
|
@ -238,46 +255,58 @@ impl PacketDecoder {
|
|||
.context("decompressing packet")?;
|
||||
|
||||
r = &self.decompress_buf;
|
||||
P::decode_packet(&mut r)?
|
||||
P::decode(&mut r)?
|
||||
} else {
|
||||
P::decode_packet(&mut r)?
|
||||
P::decode(&mut r)?
|
||||
}
|
||||
} else {
|
||||
P::decode_packet(&mut r)?
|
||||
P::decode(&mut r)?
|
||||
};
|
||||
|
||||
if !r.is_empty() {
|
||||
if log_enabled!(log::Level::Debug) {
|
||||
log::debug!("packet after partial decode: {packet:?}");
|
||||
}
|
||||
#[cfg(not(feature = "compression"))]
|
||||
let packet = P::decode(&mut r)?;
|
||||
|
||||
bail!(
|
||||
"packet contents were not read completely ({} bytes remain)",
|
||||
r.len()
|
||||
);
|
||||
}
|
||||
ensure!(
|
||||
r.is_empty(),
|
||||
"packet contents were not read completely ({} bytes remain)",
|
||||
r.len()
|
||||
);
|
||||
|
||||
let total_packet_len = VarInt(packet_len).encoded_len() + packet_len as usize;
|
||||
|
||||
self.buf.advance(total_packet_len);
|
||||
self.cursor = total_packet_len;
|
||||
|
||||
Ok(Some(packet))
|
||||
}
|
||||
|
||||
pub fn set_compression(&mut self, compression: bool) {
|
||||
self.compression = compression;
|
||||
pub fn has_next_packet(&self) -> Result<bool> {
|
||||
let mut r = &self.buf[self.cursor..];
|
||||
|
||||
match VarInt::decode_partial(&mut r) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(VarIntDecodeError::Incomplete) => Ok(false),
|
||||
Err(VarIntDecodeError::TooLarge) => bail!("malformed packet length VarInt"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
pub fn set_compression(&mut self, enabled: bool) {
|
||||
self.compression_enabled = enabled;
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn enable_encryption(&mut self, key: &[u8; 16]) {
|
||||
assert!(self.cipher.is_none(), "encryption is already enabled");
|
||||
|
||||
let mut cipher = Cipher::new(key.into(), key.into());
|
||||
// Don't forget to decrypt the data we already have.
|
||||
cipher.decrypt(&mut self.buf);
|
||||
cipher.decrypt(&mut self.buf[self.cursor..]);
|
||||
self.cipher = Some(cipher);
|
||||
}
|
||||
|
||||
pub fn queue_bytes(&mut self, mut bytes: BytesMut) {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
if let Some(cipher) = &mut self.cipher {
|
||||
cipher.decrypt(&mut bytes);
|
||||
}
|
||||
|
@ -286,8 +315,12 @@ impl PacketDecoder {
|
|||
}
|
||||
|
||||
pub fn queue_slice(&mut self, bytes: &[u8]) {
|
||||
#[cfg(feature = "encryption")]
|
||||
let len = self.buf.len();
|
||||
|
||||
self.buf.extend_from_slice(bytes);
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
if let Some(cipher) = &mut self.cipher {
|
||||
cipher.decrypt(&mut self.buf[len..]);
|
||||
}
|
||||
|
@ -308,64 +341,63 @@ impl PacketDecoder {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use super::*;
|
||||
use crate::protocol::packets::{DecodePacket, EncodePacket, PacketName};
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::entity_meta::PaintingKind;
|
||||
use crate::ident::Ident;
|
||||
use crate::item::{ItemKind, ItemStack};
|
||||
use crate::text::{Text, TextFormat};
|
||||
use crate::username::Username;
|
||||
use crate::var_long::VarLong;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
const CRYPT_KEY: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
struct TestPacket {
|
||||
string: String,
|
||||
vec_of_u16: Vec<u16>,
|
||||
u64: u64,
|
||||
#[derive(PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 42]
|
||||
struct TestPacket<'a> {
|
||||
a: bool,
|
||||
b: u8,
|
||||
c: i32,
|
||||
d: f32,
|
||||
e: f64,
|
||||
f: BlockPos,
|
||||
g: PaintingKind,
|
||||
h: Ident<&'a str>,
|
||||
i: Option<ItemStack>,
|
||||
j: Text,
|
||||
k: Username<&'a str>,
|
||||
l: VarInt,
|
||||
m: VarLong,
|
||||
n: &'a str,
|
||||
o: &'a [u8; 10],
|
||||
p: [u128; 3],
|
||||
}
|
||||
|
||||
impl PacketName for TestPacket {
|
||||
fn packet_name(&self) -> &'static str {
|
||||
"TestPacket"
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodePacket for TestPacket {
|
||||
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
self.string.encode(w)?;
|
||||
self.vec_of_u16.encode(w)?;
|
||||
self.u64.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_packet_len(&self) -> usize {
|
||||
self.string.encoded_len() + self.vec_of_u16.encoded_len() + self.u64.encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodePacket for TestPacket {
|
||||
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(TestPacket {
|
||||
string: String::decode(r).context("decoding string field")?,
|
||||
vec_of_u16: Vec::decode(r).context("decoding vec of u16 field")?,
|
||||
u64: u64::decode(r).context("decoding u64 field")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TestPacket {
|
||||
fn new(s: impl Into<String>) -> Self {
|
||||
impl<'a> TestPacket<'a> {
|
||||
fn new(n: &'a str) -> Self {
|
||||
Self {
|
||||
string: s.into(),
|
||||
vec_of_u16: vec![0x1234, 0xabcd],
|
||||
u64: 0x1122334455667788,
|
||||
a: true,
|
||||
b: 12,
|
||||
c: -999,
|
||||
d: 5.001,
|
||||
e: 1e10,
|
||||
f: BlockPos::new(1, 2, 3),
|
||||
g: PaintingKind::DonkeyKong,
|
||||
h: Ident::new("minecraft:whatever").unwrap(),
|
||||
i: Some(ItemStack::new(ItemKind::WoodenSword, 12, None)),
|
||||
j: "my ".into_text() + "fancy".italic() + " text",
|
||||
k: Username::new("00a").unwrap(),
|
||||
l: VarInt(123),
|
||||
m: VarLong(456),
|
||||
n,
|
||||
o: &[7; 10],
|
||||
p: [123456789; 3],
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&self, s: impl AsRef<str>) {
|
||||
assert_eq!(&self.string, s.as_ref());
|
||||
assert_eq!(&self.vec_of_u16, &[0x1234, 0xabcd]);
|
||||
assert_eq!(self.u64, 0x1122334455667788);
|
||||
fn check(&self, n: &'a str) {
|
||||
assert_eq!(self, &Self::new(n));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,9 +408,11 @@ mod tests {
|
|||
let mut enc = PacketEncoder::new();
|
||||
|
||||
enc.append_packet(&TestPacket::new("first")).unwrap();
|
||||
#[cfg(feature = "compression")]
|
||||
enc.set_compression(Some(0));
|
||||
enc.append_packet(&TestPacket::new("second")).unwrap();
|
||||
buf.unsplit(enc.take());
|
||||
#[cfg(feature = "encryption")]
|
||||
enc.enable_encryption(&CRYPT_KEY);
|
||||
enc.append_packet(&TestPacket::new("third")).unwrap();
|
||||
enc.prepend_packet(&TestPacket::new("fourth")).unwrap();
|
||||
|
@ -391,11 +425,13 @@ mod tests {
|
|||
.unwrap()
|
||||
.unwrap()
|
||||
.check("first");
|
||||
#[cfg(feature = "compression")]
|
||||
dec.set_compression(true);
|
||||
dec.try_next_packet::<TestPacket>()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.check("second");
|
||||
#[cfg(feature = "encryption")]
|
||||
dec.enable_encryption(&CRYPT_KEY);
|
||||
dec.try_next_packet::<TestPacket>()
|
||||
.unwrap()
|
47
valence_protocol/src/encoded_buf.rs
Normal file
47
valence_protocol/src/encoded_buf.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use std::io::Write;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use crate::{Encode, Result};
|
||||
|
||||
pub struct CachedEncode<T: ?Sized> {
|
||||
buf: Vec<u8>,
|
||||
res: Result<()>,
|
||||
_marker: PhantomData<fn(T) -> T>,
|
||||
}
|
||||
|
||||
impl<T: Encode + ?Sized> CachedEncode<T> {
|
||||
pub fn new(t: &T) -> Self {
|
||||
let mut buf = Vec::new();
|
||||
let res = t.encode(&mut buf);
|
||||
|
||||
Self {
|
||||
buf,
|
||||
res,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, t: &T) {
|
||||
self.buf.clear();
|
||||
self.res = t.encode(&mut self.buf);
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Result<Vec<u8>> {
|
||||
self.res.map(|()| self.buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Encode for CachedEncode<T> {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
match &self.res {
|
||||
Ok(()) => Ok(w.write_all(&self.buf)?),
|
||||
Err(e) => Err(anyhow!("{e:#}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
}
|
194
valence_protocol/src/entity_meta.rs
Normal file
194
valence_protocol/src/entity_meta.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
//! Types used in the entity metadata packet.
|
||||
|
||||
use crate::{Decode, Encode};
|
||||
|
||||
/// Represents an optional `u32` value excluding [`u32::MAX`].
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub struct OptionalInt(u32);
|
||||
|
||||
impl OptionalInt {
|
||||
/// Returns `None` iff `n` is Some(u32::MAX).
|
||||
pub fn new(n: impl Into<Option<u32>>) -> Option<Self> {
|
||||
match n.into() {
|
||||
None => Some(Self(0)),
|
||||
Some(u32::MAX) => None,
|
||||
Some(n) => Some(Self(n + 1)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self) -> Option<u32> {
|
||||
self.0.checked_sub(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Encode, Decode)]
|
||||
pub struct EulerAngle {
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
pub roll: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode)]
|
||||
pub enum Facing {
|
||||
Down,
|
||||
Up,
|
||||
North,
|
||||
South,
|
||||
West,
|
||||
East,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode)]
|
||||
pub struct VillagerData {
|
||||
pub kind: VillagerKind,
|
||||
pub profession: VillagerProfession,
|
||||
pub level: i32,
|
||||
}
|
||||
|
||||
impl VillagerData {
|
||||
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
profession,
|
||||
level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VillagerData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kind: Default::default(),
|
||||
profession: Default::default(),
|
||||
level: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum VillagerKind {
|
||||
Desert,
|
||||
Jungle,
|
||||
#[default]
|
||||
Plains,
|
||||
Savanna,
|
||||
Snow,
|
||||
Swamp,
|
||||
Taiga,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum VillagerProfession {
|
||||
#[default]
|
||||
None,
|
||||
Armorer,
|
||||
Butcher,
|
||||
Cartographer,
|
||||
Cleric,
|
||||
Farmer,
|
||||
Fisherman,
|
||||
Fletcher,
|
||||
Leatherworker,
|
||||
Librarian,
|
||||
Mason,
|
||||
Nitwit,
|
||||
Shepherd,
|
||||
Toolsmith,
|
||||
Weaponsmith,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum Pose {
|
||||
#[default]
|
||||
Standing,
|
||||
FallFlying,
|
||||
Sleeping,
|
||||
Swimming,
|
||||
SpinAttack,
|
||||
Sneaking,
|
||||
LongJumping,
|
||||
Dying,
|
||||
Croaking,
|
||||
UsingTongue,
|
||||
Roaring,
|
||||
Sniffing,
|
||||
Emerging,
|
||||
Digging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum BoatKind {
|
||||
#[default]
|
||||
Oak,
|
||||
Spruce,
|
||||
Birch,
|
||||
Jungle,
|
||||
Acacia,
|
||||
DarkOak,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum CatKind {
|
||||
Tabby,
|
||||
#[default]
|
||||
Black,
|
||||
Red,
|
||||
Siamese,
|
||||
BritishShorthair,
|
||||
Calico,
|
||||
Persian,
|
||||
Ragdoll,
|
||||
White,
|
||||
Jellie,
|
||||
AllBlack,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum FrogKind {
|
||||
#[default]
|
||||
Temperate,
|
||||
Warm,
|
||||
Cold,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
|
||||
pub enum PaintingKind {
|
||||
#[default]
|
||||
Kebab,
|
||||
Aztec,
|
||||
Alban,
|
||||
Aztec2,
|
||||
Bomb,
|
||||
Plant,
|
||||
Wasteland,
|
||||
Pool,
|
||||
Courbet,
|
||||
Sea,
|
||||
Sunset,
|
||||
Creebet,
|
||||
Wanderer,
|
||||
Graham,
|
||||
Match,
|
||||
Bust,
|
||||
Stage,
|
||||
Void,
|
||||
SkullAndRoses,
|
||||
Wither,
|
||||
Fighters,
|
||||
Pointer,
|
||||
Pigscene,
|
||||
BurningSkull,
|
||||
Skeleton,
|
||||
Earth,
|
||||
Wind,
|
||||
Water,
|
||||
Fire,
|
||||
DonkeyKong,
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Encode, Decode)]
|
||||
pub enum Particle {
|
||||
#[tag = 21]
|
||||
EntityEffect,
|
||||
}
|
|
@ -8,11 +8,11 @@ use std::hash::{Hash, Hasher};
|
|||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::de::Error as _;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::nbt;
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::{nbt, Decode, Encode};
|
||||
|
||||
/// A wrapper around a string type `S` which guarantees the wrapped string is a
|
||||
/// valid resource identifier.
|
||||
|
@ -97,6 +97,18 @@ impl<S: AsRef<str>> Ident<S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Consumes the identifier and returns the underlying string.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.string
|
||||
}
|
||||
|
||||
/// Consumes the identifier and returns the underlying string.
|
||||
pub fn get(self) -> S {
|
||||
self.string
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: ?Sized> Ident<&'a S> {
|
||||
/// Converts the underlying string to its owned representation and returns
|
||||
/// it as an `Ident`. This operation is infallible and no checks need to be
|
||||
/// performed.
|
||||
|
@ -110,16 +122,6 @@ impl<S: AsRef<str>> Ident<S> {
|
|||
path_start: self.path_start,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the identifier and returns the underlying string.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.string
|
||||
}
|
||||
|
||||
/// Consumes the identifier and returns the underlying string.
|
||||
pub fn get(self) -> S {
|
||||
self.string
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ident<String> {
|
||||
|
@ -225,7 +227,7 @@ where
|
|||
}
|
||||
|
||||
impl<S: Encode> Encode for Ident<S> {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, w: impl Write) -> anyhow::Result<()> {
|
||||
self.string.encode(w)
|
||||
}
|
||||
|
||||
|
@ -234,12 +236,12 @@ impl<S: Encode> Encode for Ident<S> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<S> Decode for Ident<S>
|
||||
impl<'a, S> Decode<'a> for Ident<S>
|
||||
where
|
||||
S: Decode + AsRef<str> + Send + Sync + 'static,
|
||||
S: Decode<'a> + AsRef<str>,
|
||||
{
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Ident::new(S::decode(r)?)?)
|
||||
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
|
||||
Ident::new(S::decode(r)?).map_err(|e| anyhow!("{e:#}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,7 +294,7 @@ impl<S> Error for IdentError<S> where S: AsRef<str> {}
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use valence::ident;
|
||||
/// use valence_protocol::ident;
|
||||
///
|
||||
/// let namespace = "my_namespace";
|
||||
/// let path = "my_path";
|
604
valence_protocol/src/impls.rs
Normal file
604
valence_protocol/src/impls.rs
Normal file
|
@ -0,0 +1,604 @@
|
|||
use std::borrow::Cow;
|
||||
use std::io::Write;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::ensure;
|
||||
use arrayvec::ArrayVec;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use uuid::Uuid;
|
||||
use valence_nbt::Compound;
|
||||
|
||||
use crate::var_int::VarInt;
|
||||
use crate::{Decode, Encode, Result, MAX_PACKET_SIZE};
|
||||
|
||||
// ==== Primitive ==== //
|
||||
|
||||
impl Encode for bool {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_u8(*self as u8)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for bool {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let n = r.read_u8()?;
|
||||
ensure!(n <= 1, "boolean is not 0 or 1");
|
||||
Ok(n == 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u8 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_u8(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for u8 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_u8()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i8 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_i8(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for i8 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_i8()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u16 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_u16::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for u16 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_u16::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i16 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_i16::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for i16 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_i16::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u32 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_u32::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for u32 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_u32::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i32 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_i32::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for i32 {
|
||||
fn decode(r: &mut &'_ [u8]) -> Result<Self> {
|
||||
Ok(r.read_i32::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u64 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_u64::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for u64 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_u64::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for i64 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_i64::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for u128 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_u128::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for u128 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(r.read_u128::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i128 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_i128::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for i128 {
|
||||
fn decode(r: &mut &'_ [u8]) -> Result<Self> {
|
||||
Ok(r.read_i128::<BigEndian>()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for i64 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_i64::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for f32 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
ensure!(
|
||||
self.is_finite(),
|
||||
"attempt to encode non-finite f32 ({})",
|
||||
self
|
||||
);
|
||||
Ok(w.write_f32::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for f32 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let f = r.read_f32::<BigEndian>()?;
|
||||
ensure!(f.is_finite(), "attempt to decode non-finite f32 ({f})");
|
||||
Ok(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for f64 {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
ensure!(
|
||||
self.is_finite(),
|
||||
"attempt to encode non-finite f64 ({})",
|
||||
self
|
||||
);
|
||||
Ok(w.write_f64::<BigEndian>(*self)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for f64 {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let f = r.read_f64::<BigEndian>()?;
|
||||
ensure!(f.is_finite(), "attempt to decode non-finite f64 ({f})");
|
||||
Ok(f)
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Pointer ==== //
|
||||
|
||||
impl<T: Encode + ?Sized> Encode for &T {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
(**self).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
(**self).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode + ?Sized> Encode for &mut T {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
(**self).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
(**self).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode + ?Sized> Encode for Box<T> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_ref().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Decode<'a>> Decode<'a> for Box<T> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
T::decode(r).map(Box::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode + ?Sized> Encode for Rc<T> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_ref().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Decode<'a>> Decode<'a> for Rc<T> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
T::decode(r).map(Rc::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode + ?Sized> Encode for Arc<T> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_ref().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Decode<'a>> Decode<'a> for Arc<T> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
T::decode(r).map(Arc::new)
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Tuple ==== //
|
||||
|
||||
macro_rules! impl_tuple {
|
||||
($($ty:ident)*) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($ty: Encode,)*> Encode for ($($ty,)*) {
|
||||
fn encode(&self, mut _w: impl Write) -> Result<()> {
|
||||
let ($($ty,)*) = self;
|
||||
$(
|
||||
$ty.encode(&mut _w)?;
|
||||
)*
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
let ($($ty,)*) = self;
|
||||
0 $(+ $ty.encoded_len())*
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, $($ty: Decode<'a>,)*> Decode<'a> for ($($ty,)*) {
|
||||
fn decode(_r: &mut &'a [u8]) -> Result<Self> {
|
||||
Ok(($($ty::decode(_r)?,)*))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_tuple!();
|
||||
impl_tuple!(A);
|
||||
impl_tuple!(A B);
|
||||
impl_tuple!(A B C);
|
||||
impl_tuple!(A B C D);
|
||||
impl_tuple!(A B C D E);
|
||||
impl_tuple!(A B C D E F);
|
||||
impl_tuple!(A B C D E F G);
|
||||
impl_tuple!(A B C D E F G H);
|
||||
impl_tuple!(A B C D E F G H I);
|
||||
impl_tuple!(A B C D E F G H I J);
|
||||
impl_tuple!(A B C D E F G H I J K);
|
||||
impl_tuple!(A B C D E F G H I J K L);
|
||||
|
||||
// ==== Sequence ==== //
|
||||
|
||||
/// Like tuples, arrays are encoded and decoded without a VarInt length prefix.
|
||||
impl<const N: usize, T: Encode> Encode for [T; N] {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
for t in self {
|
||||
t.encode(&mut w)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.iter().map(|t| t.encoded_len()).sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize, T: Decode<'a>> Decode<'a> for [T; N] {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
// TODO: rewrite using std::array::try_from_fn when stabilized.
|
||||
|
||||
let mut elems = ArrayVec::new();
|
||||
for _ in 0..N {
|
||||
elems.push(T::decode(r)?);
|
||||
}
|
||||
|
||||
elems.into_inner().map_err(|_| unreachable!())
|
||||
}
|
||||
}
|
||||
|
||||
/// References to fixed-length arrays are not length prefixed.
|
||||
impl<'a, const N: usize> Decode<'a> for &'a [u8; N] {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
ensure!(
|
||||
r.len() >= N,
|
||||
"not enough data to decode u8 array of length {N}"
|
||||
);
|
||||
|
||||
let (res, remaining) = r.split_at(N);
|
||||
let arr = <&[u8; N]>::try_from(res).unwrap();
|
||||
*r = remaining;
|
||||
Ok(arr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for [T] {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
let len = self.len();
|
||||
ensure!(
|
||||
len <= i32::MAX as usize,
|
||||
"length of slice ({len}) exceeds i32::MAX"
|
||||
);
|
||||
|
||||
VarInt(len as i32).encode(&mut w)?;
|
||||
for t in self {
|
||||
t.encode(&mut w)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
let elems_len: usize = self.iter().map(|a| a.encoded_len()).sum();
|
||||
VarInt(self.len().try_into().unwrap_or(i32::MAX)).encoded_len() + elems_len
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decode<'a> for &'a [u8] {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
let len = VarInt::decode(r)?.0;
|
||||
ensure!(len >= 0, "attempt to decode slice with negative length");
|
||||
let len = len as usize;
|
||||
ensure!(r.len() >= len, "not enough data remaining to decode slice");
|
||||
|
||||
let (res, remaining) = r.split_at(len);
|
||||
*r = remaining;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encode> Encode for Vec<T> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_slice().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_slice().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Decode<'a>> Decode<'a> for Vec<T> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
let len = VarInt::decode(r)?.0;
|
||||
ensure!(len >= 0, "attempt to decode Vec with negative length");
|
||||
let len = len as usize;
|
||||
|
||||
// Don't allocate more memory than what would roughly fit in a single packet in
|
||||
// case we get a malicious array length.
|
||||
let cap = (MAX_PACKET_SIZE as usize / mem::size_of::<T>().max(1)).min(len);
|
||||
let mut vec = Vec::with_capacity(cap);
|
||||
|
||||
for _ in 0..len {
|
||||
vec.push(T::decode(r)?);
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Decode<'a>> Decode<'a> for Box<[T]> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
Ok(Vec::decode(r)?.into_boxed_slice())
|
||||
}
|
||||
}
|
||||
|
||||
// ==== String ==== //
|
||||
|
||||
impl Encode for str {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
let len = self.len();
|
||||
ensure!(
|
||||
len <= i32::MAX as usize,
|
||||
"byte length of string ({len}) exceeds i32::MAX"
|
||||
);
|
||||
|
||||
VarInt(self.len() as i32).encode(&mut w)?;
|
||||
Ok(w.write_all(self.as_bytes())?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(self.len().try_into().unwrap_or(i32::MAX)).encoded_len() + self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decode<'a> for &'a str {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
let len = VarInt::decode(r)?.0;
|
||||
ensure!(len >= 0, "attempt to decode struct with negative length");
|
||||
let len = len as usize;
|
||||
ensure!(r.len() >= len, "not enough data remaining to decode string");
|
||||
|
||||
let (res, remaining) = r.split_at(len);
|
||||
*r = remaining;
|
||||
|
||||
Ok(std::str::from_utf8(res)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for String {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_str().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_str().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for String {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(<&str>::decode(r)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for Box<str> {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(<&str>::decode(r)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Other ==== //
|
||||
|
||||
impl<T: Encode> Encode for Option<T> {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
match self {
|
||||
Some(t) => {
|
||||
true.encode(&mut w)?;
|
||||
t.encode(w)
|
||||
}
|
||||
None => false.encode(w),
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
1 + self.as_ref().map_or(0, |t| t.encoded_len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Decode<'a>> Decode<'a> for Option<T> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
Ok(match bool::decode(r)? {
|
||||
true => Some(T::decode(r)?),
|
||||
false => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B> Encode for Cow<'a, B>
|
||||
where
|
||||
B: ToOwned + Encode,
|
||||
{
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_ref().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.as_ref().encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B> Decode<'a> for Cow<'a, B>
|
||||
where
|
||||
B: ToOwned,
|
||||
&'a B: Decode<'a>,
|
||||
{
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
<&B>::decode(r).map(Cow::Borrowed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Uuid {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.as_u128().encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decode<'a> for Uuid {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
u128::decode(r).map(Uuid::from_u128)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Compound {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
Ok(valence_nbt::to_binary_writer(w, self, "")?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.binary_encoded_len("")
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for Compound {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
Ok(valence_nbt::from_binary_slice(r)?.0)
|
||||
}
|
||||
}
|
146
valence_protocol/src/item.rs
Normal file
146
valence_protocol/src/item.rs
Normal file
|
@ -0,0 +1,146 @@
|
|||
use std::io::Write;
|
||||
|
||||
use anyhow::{ensure, Context};
|
||||
use valence_nbt::Compound;
|
||||
|
||||
use crate::block::BlockKind;
|
||||
use crate::var_int::VarInt;
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/item.rs"));
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct ItemStack {
|
||||
pub item: ItemKind,
|
||||
count: u8,
|
||||
pub nbt: Option<Compound>,
|
||||
}
|
||||
|
||||
const STACK_MIN: u8 = 1;
|
||||
const STACK_MAX: u8 = 127;
|
||||
|
||||
impl ItemStack {
|
||||
pub fn new(item: ItemKind, count: u8, nbt: Option<Compound>) -> Self {
|
||||
Self {
|
||||
item,
|
||||
count: count.clamp(STACK_MIN, STACK_MAX),
|
||||
nbt,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the number of items in this stack.
|
||||
pub fn count(&self) -> u8 {
|
||||
self.count
|
||||
}
|
||||
|
||||
/// Sets the number of items in this stack. Values are clamped to 1-127,
|
||||
/// which are the positive values accepted by clients.
|
||||
pub fn set_count(&mut self, count: u8) {
|
||||
self.count = count.clamp(STACK_MIN, STACK_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Option<ItemStack> {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
match self {
|
||||
None => false.encode(w),
|
||||
Some(s) => {
|
||||
true.encode(&mut w)?;
|
||||
s.item.encode(&mut w)?;
|
||||
s.count.encode(&mut w)?;
|
||||
match &s.nbt {
|
||||
Some(n) => n.encode(w),
|
||||
None => 0u8.encode(w),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
match self {
|
||||
None => 1,
|
||||
Some(s) => {
|
||||
1 + s.item.encoded_len()
|
||||
+ 1
|
||||
+ s.nbt.as_ref().map(|nbt| nbt.encoded_len()).unwrap_or(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for Option<ItemStack> {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let present = bool::decode(r)?;
|
||||
if !present {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let item = ItemKind::decode(r)?;
|
||||
let count = u8::decode(r)?;
|
||||
|
||||
ensure!(
|
||||
(STACK_MIN..=STACK_MAX).contains(&count),
|
||||
"invalid item stack count (got {count}, expected {STACK_MIN}..={STACK_MAX})"
|
||||
);
|
||||
|
||||
let nbt = if let [0, rest @ ..] = *r {
|
||||
*r = rest;
|
||||
None
|
||||
} else {
|
||||
Some(Compound::decode(r)?)
|
||||
};
|
||||
|
||||
Ok(Some(ItemStack { item, count, nbt }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for ItemKind {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
VarInt(self.to_raw() as i32).encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
VarInt(self.to_raw() as i32).encoded_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_> for ItemKind {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let id = VarInt::decode(r)?.0;
|
||||
let errmsg = "invalid item ID";
|
||||
|
||||
ItemKind::from_raw(id.try_into().context(errmsg)?).context(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn item_kind_to_block_kind() {
|
||||
assert_eq!(
|
||||
ItemKind::Cauldron.to_block_kind(),
|
||||
Some(BlockKind::Cauldron)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_state_to_item() {
|
||||
assert_eq!(BlockKind::Torch.to_item_kind(), ItemKind::Torch);
|
||||
assert_eq!(BlockKind::WallTorch.to_item_kind(), ItemKind::Torch);
|
||||
|
||||
assert_eq!(BlockKind::Cauldron.to_item_kind(), ItemKind::Cauldron);
|
||||
assert_eq!(BlockKind::LavaCauldron.to_item_kind(), ItemKind::Cauldron);
|
||||
|
||||
assert_eq!(BlockKind::NetherPortal.to_item_kind(), ItemKind::Air);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_stack_clamps_count() {
|
||||
let mut stack = ItemStack::new(ItemKind::Stone, 200, None);
|
||||
assert_eq!(stack.count, STACK_MAX);
|
||||
stack.set_count(201);
|
||||
assert_eq!(stack.count, STACK_MAX);
|
||||
}
|
||||
}
|
410
valence_protocol/src/lib.rs
Normal file
410
valence_protocol/src/lib.rs
Normal file
|
@ -0,0 +1,410 @@
|
|||
//! A library for interacting with the Minecraft (Java Edition) network
|
||||
//! protocol.
|
||||
//!
|
||||
//! The API is centered around the [`Encode`] and [`Decode`] traits. Clientbound
|
||||
//! and serverbound packets are defined in the [`packets`] module. Packets are
|
||||
//! encoded and decoded using the [`PacketEncoder`] and [`PacketDecoder`] types.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use valence_protocol::codec::{PacketDecoder, PacketEncoder};
|
||||
//! use valence_protocol::packets::c2s::play::RenameItem;
|
||||
//!
|
||||
//! let mut enc = PacketEncoder::new();
|
||||
//!
|
||||
//! let outgoing = RenameItem {
|
||||
//! item_name: "Hello!",
|
||||
//! };
|
||||
//!
|
||||
//! enc.append_packet(&outgoing).unwrap();
|
||||
//!
|
||||
//! let mut dec = PacketDecoder::new();
|
||||
//!
|
||||
//! dec.queue_bytes(enc.take());
|
||||
//!
|
||||
//! let incoming = dec.try_next_packet::<RenameItem>().unwrap().unwrap();
|
||||
//!
|
||||
//! assert_eq!(outgoing.item_name, incoming.item_name);
|
||||
//! ```
|
||||
//!
|
||||
//! # Stability
|
||||
//!
|
||||
//! The Minecraft protocol is not stable. Updates to Minecraft may change the
|
||||
//! protocol in subtle or drastic ways. In response to this, `valence_protocol`
|
||||
//! aims to support only the most recent version of the game (excluding
|
||||
//! snapshots, pre-releases, etc). An update to Minecraft often requires a
|
||||
//! breaking change to the library.
|
||||
//!
|
||||
//! `valence_protocol` is versioned in lockstep with `valence`. The currently
|
||||
//! supported Minecraft version can be checked with the [`PROTOCOL_VERSION`] or
|
||||
//! [`MINECRAFT_VERSION`] constants.
|
||||
//!
|
||||
//! # Feature Flags
|
||||
//!
|
||||
//! TODO
|
||||
//!
|
||||
//! [`PacketEncoder`]: codec::PacketEncoder
|
||||
//! [`PacketDecoder`]: codec::PacketDecoder
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(
|
||||
rustdoc::broken_intra_doc_links,
|
||||
rustdoc::private_intra_doc_links,
|
||||
rustdoc::missing_crate_level_docs,
|
||||
rustdoc::invalid_codeblock_attributes,
|
||||
rustdoc::invalid_rust_codeblocks,
|
||||
rustdoc::bare_urls
|
||||
)]
|
||||
#![warn(
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_lifetimes,
|
||||
unused_import_braces,
|
||||
clippy::dbg_macro
|
||||
)]
|
||||
#![allow(
|
||||
clippy::derive_partial_eq_without_eq,
|
||||
clippy::unusual_byte_groupings,
|
||||
clippy::comparison_chain
|
||||
)]
|
||||
|
||||
// Allows us to use our own proc macros internally.
|
||||
extern crate self as valence_protocol;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
pub use anyhow::{Error, Result};
|
||||
pub use valence_derive::{Decode, Encode, Packet};
|
||||
pub use valence_nbt as nbt;
|
||||
|
||||
use crate::byte_counter::ByteCounter;
|
||||
|
||||
/// The Minecraft protocol version this library currently targets.
|
||||
pub const PROTOCOL_VERSION: i32 = 760;
|
||||
|
||||
/// The stringized name of the Minecraft version this library currently targets.
|
||||
pub const MINECRAFT_VERSION: &str = "1.19.2";
|
||||
|
||||
pub mod block;
|
||||
pub mod block_pos;
|
||||
pub mod bounded;
|
||||
pub mod byte_angle;
|
||||
mod byte_counter;
|
||||
pub mod codec;
|
||||
pub mod enchant;
|
||||
pub mod encoded_buf;
|
||||
pub mod entity_meta;
|
||||
pub mod ident;
|
||||
mod impls;
|
||||
pub mod item;
|
||||
pub mod packets;
|
||||
pub mod raw_bytes;
|
||||
pub mod text;
|
||||
pub mod types;
|
||||
pub mod username;
|
||||
pub mod var_int;
|
||||
pub mod var_long;
|
||||
|
||||
/// Used only by proc macros. Not public API.
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
|
||||
pub use crate::var_int::VarInt;
|
||||
pub use crate::{Decode, DerivedPacketDecode, DerivedPacketEncode, Encode};
|
||||
}
|
||||
|
||||
/// The maximum number of bytes in a single Minecraft packet.
|
||||
pub const MAX_PACKET_SIZE: i32 = 2097152;
|
||||
|
||||
/// The `Encode` trait allows objects to be written to the Minecraft protocol.
|
||||
/// It is the inverse of [`Decode`].
|
||||
///
|
||||
/// # Deriving
|
||||
///
|
||||
/// This trait can be implemented automatically for structs and enums by using
|
||||
/// the [`Encode`][macro] derive macro. All components of the type must
|
||||
/// implement `Encode`. Components are encoded in the order they appear in the
|
||||
/// type definition.
|
||||
///
|
||||
/// If a `#[packet_id = ...]` attribute is present, encoding the type begins by
|
||||
/// writing the specified constant [`VarInt`] value before any of the
|
||||
/// components.
|
||||
///
|
||||
/// For enums, the variant to encode is marked by a leading [`VarInt`]
|
||||
/// discriminant (tag). The discriminant value can be changed using the `#[tag =
|
||||
/// ...]` attribute on the variant in question. Discriminant values are assigned
|
||||
/// to variants using rules similar to regular enum discriminants.
|
||||
///
|
||||
/// [`VarInt`]: var_int::VarInt
|
||||
///
|
||||
/// ```
|
||||
/// use valence_protocol::Encode;
|
||||
///
|
||||
/// #[derive(Encode)]
|
||||
/// #[packet_id = 42]
|
||||
/// struct MyStruct<'a> {
|
||||
/// first: i32,
|
||||
/// second: &'a str,
|
||||
/// third: [f64; 3],
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Encode)]
|
||||
/// enum MyEnum {
|
||||
/// First, // tag = 0
|
||||
/// Second, // tag = 1
|
||||
/// #[tag = 25]
|
||||
/// Third, // tag = 25
|
||||
/// Fourth, // tag = 26
|
||||
/// }
|
||||
///
|
||||
/// let value = MyStruct {
|
||||
/// first: 10,
|
||||
/// second: "hello",
|
||||
/// third: [1.5, 3.14, 2.718],
|
||||
/// };
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// value.encode(&mut buf).unwrap();
|
||||
///
|
||||
/// println!("{buf:?}");
|
||||
/// ```
|
||||
///
|
||||
/// [macro]: valence_derive::Encode
|
||||
pub trait Encode {
|
||||
/// Writes this object to the provided writer.
|
||||
///
|
||||
/// If this type also implements [`Decode`] then successful calls to this
|
||||
/// function returning `Ok(())` must always successfully [`decode`] using
|
||||
/// the data that was written to the writer. The exact number of bytes
|
||||
/// that were originally written must be consumed during the decoding.
|
||||
///
|
||||
/// Additionally, this function must be pure. If no write error occurs,
|
||||
/// successive calls to `encode` must write the same bytes to the writer
|
||||
/// argument. This property can be broken by using internal mutability,
|
||||
/// global state, or other tricks.
|
||||
///
|
||||
/// [`decode`]: Decode::decode
|
||||
fn encode(&self, w: impl Write) -> Result<()>;
|
||||
|
||||
/// Returns the number of bytes that will be written when [`Self::encode`]
|
||||
/// is called.
|
||||
///
|
||||
/// If [`Self::encode`] returns `Ok`, then the exact number of bytes
|
||||
/// reported by this function must be written to the writer argument.
|
||||
///
|
||||
/// If the result is `Err`, then the number of written bytes must be less
|
||||
/// than or equal to the count returned by this function.
|
||||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Calls [`Self::encode`] to count the number of written bytes. This is
|
||||
/// always correct, but is not always the most efficient approach.
|
||||
fn encoded_len(&self) -> usize {
|
||||
let mut counter = ByteCounter::new();
|
||||
let _ = self.encode(&mut counter);
|
||||
counter.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Decode` trait allows objects to be read from the Minecraft protocol. It
|
||||
/// is the inverse of [`Encode`].
|
||||
///
|
||||
/// `Decode` is parameterized by a lifetime. This allows the decoded value to
|
||||
/// borrow data from the byte slice it was read from.
|
||||
///
|
||||
/// # Deriving
|
||||
///
|
||||
/// This trait can be implemented automatically for structs and enums by using
|
||||
/// the [`Decode`][macro] derive macro. All components of the type must
|
||||
/// implement `Decode`. Components are decoded in the order they appear in the
|
||||
/// type definition.
|
||||
///
|
||||
/// If a `#[packet_id = ...]` attribute is present, encoding the type begins by
|
||||
/// reading the specified constant [`VarInt`] value before any of the
|
||||
/// components.
|
||||
///
|
||||
/// For enums, the variant to decode is determined by a leading [`VarInt`]
|
||||
/// discriminant (tag). The discriminant value can be changed using the `#[tag =
|
||||
/// ...]` attribute on the variant in question. Discriminant values are assigned
|
||||
/// to variants using rules similar to regular enum discriminants.
|
||||
///
|
||||
/// [`VarInt`]: var_int::VarInt
|
||||
///
|
||||
/// ```
|
||||
/// use valence_protocol::Decode;
|
||||
///
|
||||
/// #[derive(PartialEq, Debug, Decode)]
|
||||
/// #[packet_id = 5]
|
||||
/// struct MyStruct {
|
||||
/// first: i32,
|
||||
/// second: MyEnum,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(PartialEq, Debug, Decode)]
|
||||
/// enum MyEnum {
|
||||
/// First, // tag = 0
|
||||
/// Second, // tag = 1
|
||||
/// #[tag = 25]
|
||||
/// Third, // tag = 25
|
||||
/// Fourth, // tag = 26
|
||||
/// }
|
||||
///
|
||||
/// let mut r: &[u8] = &[5, 0, 0, 0, 0, 26];
|
||||
///
|
||||
/// let value = MyStruct::decode(&mut r).unwrap();
|
||||
/// let expected = MyStruct {
|
||||
/// first: 0,
|
||||
/// second: MyEnum::Fourth,
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(value, expected);
|
||||
/// assert!(r.is_empty());
|
||||
/// ```
|
||||
///
|
||||
/// [macro]: valence_derive::Decode
|
||||
pub trait Decode<'a>: Sized {
|
||||
/// Reads this object from the provided byte slice.
|
||||
///
|
||||
/// Implementations of `Decode` are expected to shrink the slice from the
|
||||
/// front as bytes are read.
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self>;
|
||||
}
|
||||
|
||||
/// Marker for types that are encoded or decoded as complete packets.
|
||||
///
|
||||
/// A complete packet is data starting with a [`VarInt`] packet ID. [`Encode`]
|
||||
/// and [`Decode`] implementations on `Self`, if present, are expected to handle
|
||||
/// this leading `VarInt`.
|
||||
///
|
||||
/// [`VarInt`]: var_int::VarInt
|
||||
pub trait Packet {
|
||||
/// The name of this packet.
|
||||
///
|
||||
/// This is usually the name of the type representing the packet without any
|
||||
/// generic parameters or other decorations.
|
||||
fn packet_name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
/// Packets which obtained [`Encode`] implementations via the [`Encode`][macro]
|
||||
/// derive macro with the `#[packet_id = ...]` attribute.
|
||||
///
|
||||
/// Along with [`DerivedPacketDecode`], this trait can be occasionally useful
|
||||
/// for automating tasks such as defining large packet enums. Otherwise, this
|
||||
/// trait should not be used and has thus been hidden from the documentation.
|
||||
///
|
||||
/// [macro]: valence_derive::Encode
|
||||
#[doc(hidden)]
|
||||
pub trait DerivedPacketEncode: Encode {
|
||||
/// The ID of this packet specified with `#[packet_id = ...]`.
|
||||
const ID: i32;
|
||||
/// The name of the type implementing this trait.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Like [`Encode::encode`], but does not write a leading [`VarInt`] packet
|
||||
/// ID.
|
||||
fn encode_without_id(&self, w: impl Write) -> Result<()>;
|
||||
/// Like [`Encode::encoded_len`], but does not count a leading [`VarInt`]
|
||||
/// packet ID.
|
||||
fn encoded_len_without_id(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Packets which obtained [`Decode`] implementations via the [`Decode`][macro]
|
||||
/// derive macro with the `#[packet_id = ...]` attribute.
|
||||
///
|
||||
/// Along with [`DerivedPacketEncode`], this trait can be occasionally useful
|
||||
/// for automating tasks such as defining large packet enums. Otherwise, this
|
||||
/// trait should not be used and has thus been hidden from the documentation.
|
||||
///
|
||||
/// [macro]: valence_derive::Decode
|
||||
#[doc(hidden)]
|
||||
pub trait DerivedPacketDecode<'a>: Decode<'a> {
|
||||
/// The ID of this packet specified with `#[packet_id = ...]`.
|
||||
const ID: i32;
|
||||
/// The name of the type implementing this trait.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Like [`Decode::decode`], but does not decode a leading [`VarInt`] packet
|
||||
/// ID.
|
||||
fn decode_without_id(r: &mut &'a [u8]) -> Result<Self>;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
mod derive_tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 1]
|
||||
struct RegularStruct {
|
||||
foo: i32,
|
||||
bar: bool,
|
||||
baz: f64,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 2]
|
||||
struct UnitStruct;
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 3]
|
||||
struct EmptyStruct {}
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 4]
|
||||
struct TupleStruct(i32, bool, f64);
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 5]
|
||||
struct StructWithGenerics<'z, T = ()> {
|
||||
foo: &'z str,
|
||||
bar: T,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 6]
|
||||
struct TupleStructWithGenerics<'z, T = ()>(&'z str, i32, T);
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 7]
|
||||
enum RegularEnum {
|
||||
Empty,
|
||||
Tuple(i32, bool, f64),
|
||||
Fields { foo: i32, bar: bool, baz: f64 },
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 8]
|
||||
enum EmptyEnum {}
|
||||
|
||||
#[derive(Encode, Decode, Packet)]
|
||||
#[packet_id = 0xbeef]
|
||||
enum EnumWithGenericsAndTags<'z, T = ()> {
|
||||
#[tag = 5]
|
||||
First {
|
||||
foo: &'z str,
|
||||
},
|
||||
Second(&'z str),
|
||||
#[tag = 0xff]
|
||||
Third,
|
||||
#[tag = 0]
|
||||
Fourth(T),
|
||||
}
|
||||
|
||||
#[allow(unconditional_recursion)]
|
||||
fn has_impls<'a, T>()
|
||||
where
|
||||
T: Encode + Decode<'a> + DerivedPacketEncode + DerivedPacketDecode<'a> + Packet,
|
||||
{
|
||||
has_impls::<RegularStruct>();
|
||||
has_impls::<UnitStruct>();
|
||||
has_impls::<EmptyStruct>();
|
||||
has_impls::<TupleStruct>();
|
||||
has_impls::<StructWithGenerics>();
|
||||
has_impls::<TupleStructWithGenerics>();
|
||||
has_impls::<RegularEnum>();
|
||||
has_impls::<EmptyEnum>();
|
||||
has_impls::<EnumWithGenericsAndTags>();
|
||||
}
|
||||
}
|
157
valence_protocol/src/packets.rs
Normal file
157
valence_protocol/src/packets.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
//! Contains client-to-server ([`c2s`]) and server-to-client ([`s2c`]) packets
|
||||
//! for the current version of the game.
|
||||
//!
|
||||
//! If the packets as defined do not meet your needs, consider using the tools
|
||||
//! in this library to redefine the packets yourself.
|
||||
|
||||
pub use c2s::handshake::C2sHandshakePacket;
|
||||
pub use c2s::login::C2sLoginPacket;
|
||||
pub use c2s::play::C2sPlayPacket;
|
||||
pub use c2s::status::C2sStatusPacket;
|
||||
pub use s2c::login::S2cLoginPacket;
|
||||
pub use s2c::play::S2cPlayPacket;
|
||||
pub use s2c::status::S2cStatusPacket;
|
||||
|
||||
/// Defines an enum of packets.
|
||||
macro_rules! packet_enum {
|
||||
(
|
||||
$(#[$attrs:meta])*
|
||||
$enum_name:ident<$enum_life:lifetime> {
|
||||
$($packet:ident $(<$life:lifetime>)?),* $(,)?
|
||||
}
|
||||
) => {
|
||||
$(#[$attrs])*
|
||||
pub enum $enum_name<$enum_life> {
|
||||
$(
|
||||
$packet($packet $(<$life>)?),
|
||||
)*
|
||||
}
|
||||
|
||||
$(
|
||||
impl<$enum_life> From<$packet $(<$life>)?> for $enum_name<$enum_life> {
|
||||
fn from(p: $packet $(<$life>)?) -> Self {
|
||||
Self::$packet(p)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
impl<$enum_life> crate::Encode for $enum_name<$enum_life> {
|
||||
fn encode(&self, mut w: impl std::io::Write) -> crate::Result<()> {
|
||||
use crate::DerivedPacketEncode;
|
||||
use crate::var_int::VarInt;
|
||||
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => {
|
||||
VarInt($packet::ID).encode(&mut w)?;
|
||||
pkt.encode_without_id(w)?;
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<$enum_life> crate::Decode<$enum_life> for $enum_name<$enum_life> {
|
||||
fn decode(r: &mut &$enum_life [u8]) -> crate::Result<Self> {
|
||||
use crate::DerivedPacketDecode;
|
||||
use crate::var_int::VarInt;
|
||||
|
||||
let id = VarInt::decode(r)?.0;
|
||||
Ok(match id {
|
||||
$(
|
||||
$packet::ID => Self::$packet($packet::decode_without_id(r)?),
|
||||
)*
|
||||
id => anyhow::bail!("unknown packet id {}", id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<$enum_life> crate::Packet for $enum_name<$enum_life> {
|
||||
fn packet_name(&self) -> &'static str {
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => pkt.packet_name(),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// No lifetime on the enum in this case.
|
||||
(
|
||||
$(#[$attrs:meta])*
|
||||
$enum_name:ident {
|
||||
$($packet:ident),* $(,)?
|
||||
}
|
||||
) => {
|
||||
$(#[$attrs])*
|
||||
pub enum $enum_name {
|
||||
$(
|
||||
$packet($packet),
|
||||
)*
|
||||
}
|
||||
|
||||
$(
|
||||
impl From<$packet> for $enum_name {
|
||||
fn from(p: $packet) -> Self {
|
||||
Self::$packet(p)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
impl crate::Encode for $enum_name {
|
||||
fn encode(&self, mut w: impl std::io::Write) -> crate::Result<()> {
|
||||
use crate::DerivedPacketEncode;
|
||||
use crate::var_int::VarInt;
|
||||
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => {
|
||||
VarInt($packet::ID).encode(&mut w)?;
|
||||
pkt.encode_without_id(w)?;
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Decode<'_> for $enum_name {
|
||||
fn decode(r: &mut &[u8]) -> crate::Result<Self> {
|
||||
use crate::DerivedPacketDecode;
|
||||
use crate::var_int::VarInt;
|
||||
|
||||
let id = VarInt::decode(r)?.0;
|
||||
Ok(match id {
|
||||
$(
|
||||
$packet::ID => Self::$packet($packet::decode_without_id(r)?),
|
||||
)*
|
||||
id => anyhow::bail!("unknown packet id {}", id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Packet for $enum_name {
|
||||
fn packet_name(&self) -> &'static str {
|
||||
match self {
|
||||
$(
|
||||
Self::$packet(pkt) => pkt.packet_name(),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod c2s;
|
||||
pub mod s2c;
|
560
valence_protocol/src/packets/c2s.rs
Normal file
560
valence_protocol/src/packets/c2s.rs
Normal file
|
@ -0,0 +1,560 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::block::BlockFace;
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::ident::Ident;
|
||||
use crate::item::ItemStack;
|
||||
use crate::raw_bytes::RawBytes;
|
||||
use crate::types::{
|
||||
Action, ChatMode, ClickContainerMode, CommandArgumentSignature, CommandBlockFlags,
|
||||
CommandBlockMode, DiggingStatus, DisplayedSkinParts, EntityInteraction, Hand,
|
||||
HandshakeNextState, MainHand, MessageAcknowledgment, MsgSigOrVerifyToken, PlayerInputFlags,
|
||||
PublicKeyData, RecipeBookId, StructureBlockAction, StructureBlockFlags, StructureBlockMirror,
|
||||
StructureBlockMode, StructureBlockRotation,
|
||||
};
|
||||
use crate::username::Username;
|
||||
use crate::var_int::VarInt;
|
||||
use crate::var_long::VarLong;
|
||||
use crate::{Decode, Encode, Packet};
|
||||
|
||||
pub mod handshake {
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct Handshake<'a> {
|
||||
pub protocol_version: VarInt,
|
||||
pub server_address: &'a str,
|
||||
pub server_port: u16,
|
||||
pub next_state: HandshakeNextState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct HandshakeOwned {
|
||||
pub protocol_version: VarInt,
|
||||
pub server_address: String,
|
||||
pub server_port: u16,
|
||||
pub next_state: HandshakeNextState,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
C2sHandshakePacket<'a> {
|
||||
Handshake<'a>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod status {
|
||||
use super::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct StatusRequest;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x01]
|
||||
pub struct PingRequest {
|
||||
pub payload: u64,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
C2sStatusPacket {
|
||||
StatusRequest,
|
||||
PingRequest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod login {
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct LoginStart<'a> {
|
||||
pub username: Username<&'a str>,
|
||||
pub sig_data: Option<PublicKeyData<'a>>,
|
||||
pub profile_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x01]
|
||||
pub struct EncryptionResponse<'a> {
|
||||
pub shared_secret: &'a [u8],
|
||||
pub sig_or_token: MsgSigOrVerifyToken<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x02]
|
||||
pub struct LoginPluginResponse<'a> {
|
||||
pub message_id: VarInt,
|
||||
pub data: Option<RawBytes<'a>>,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
C2sLoginPacket<'a> {
|
||||
LoginStart<'a>,
|
||||
EncryptionResponse<'a>,
|
||||
LoginPluginResponse<'a>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod play {
|
||||
use super::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct ConfirmTeleport {
|
||||
pub teleport_id: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x01]
|
||||
pub struct QueryBlockEntityTag {
|
||||
pub transaction_id: VarInt,
|
||||
pub location: BlockPos,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x02]
|
||||
pub enum ChangeDifficulty {
|
||||
Peaceful,
|
||||
Easy,
|
||||
Normal,
|
||||
Hard,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x03]
|
||||
pub struct MessageAcknowledgmentC2s<'a>(pub MessageAcknowledgment<'a>);
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x04]
|
||||
pub struct ChatCommand<'a> {
|
||||
pub command: &'a str,
|
||||
pub timestamp: u64,
|
||||
pub salt: u64,
|
||||
pub argument_signatures: Vec<CommandArgumentSignature<'a>>,
|
||||
pub signed_preview: bool,
|
||||
pub acknowledgement: MessageAcknowledgment<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x05]
|
||||
pub struct ChatMessage<'a> {
|
||||
pub message: &'a str,
|
||||
pub timestamp: u64,
|
||||
pub salt: u64,
|
||||
pub signature: &'a [u8],
|
||||
pub signed_preview: bool,
|
||||
pub acknowledgement: MessageAcknowledgment<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x06]
|
||||
pub struct ChatPreviewC2s {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x07]
|
||||
pub enum ClientCommand {
|
||||
PerformRespawn,
|
||||
RequestStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x08]
|
||||
pub struct ClientInformation<'a> {
|
||||
pub locale: &'a str,
|
||||
pub view_distance: u8,
|
||||
pub chat_mode: ChatMode,
|
||||
pub chat_colors: bool,
|
||||
pub displayed_skin_parts: DisplayedSkinParts,
|
||||
pub main_hand: MainHand,
|
||||
pub enable_text_filtering: bool,
|
||||
pub allow_server_listings: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x09]
|
||||
pub struct CommandSuggestionsRequest<'a> {
|
||||
pub transaction_id: VarInt,
|
||||
pub text: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0a]
|
||||
pub struct ClickContainerButton {
|
||||
pub window_id: i8,
|
||||
pub button_id: i8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0b]
|
||||
pub struct ClickContainer {
|
||||
pub window_id: u8,
|
||||
pub state_id: VarInt,
|
||||
pub slot_idx: i16,
|
||||
pub button: i8,
|
||||
pub mode: ClickContainerMode,
|
||||
pub slots: Vec<(i16, Option<ItemStack>)>,
|
||||
pub carried_item: Option<ItemStack>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0c]
|
||||
pub struct CloseContainerC2s {
|
||||
pub window_id: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0d]
|
||||
pub struct PluginMessageC2s<'a> {
|
||||
pub channel: Ident<&'a str>,
|
||||
pub data: RawBytes<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0e]
|
||||
pub struct EditBook<'a> {
|
||||
pub slot: VarInt,
|
||||
pub entries: Vec<&'a str>,
|
||||
pub title: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0f]
|
||||
pub struct QueryEntityTag {
|
||||
pub transaction_id: VarInt,
|
||||
pub entity_id: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x10]
|
||||
pub struct Interact {
|
||||
pub entity_id: VarInt,
|
||||
pub interact: EntityInteraction,
|
||||
pub sneaking: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x11]
|
||||
pub struct JigsawGenerate {
|
||||
pub location: BlockPos,
|
||||
pub levels: VarInt,
|
||||
pub keep_jigsaws: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x12]
|
||||
pub struct KeepAliveC2s {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x13]
|
||||
pub struct LockDifficulty {
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x14]
|
||||
pub struct SetPlayerPosition {
|
||||
pub position: [f64; 3],
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x15]
|
||||
pub struct SetPlayerPositionAndRotation {
|
||||
pub position: [f64; 3],
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x16]
|
||||
pub struct SetPlayerRotation {
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x17]
|
||||
pub struct SetPlayerOnGround(pub bool);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x18]
|
||||
pub struct MoveVehicleC2s {
|
||||
pub position: [f64; 3],
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x19]
|
||||
pub struct PaddleBoat {
|
||||
pub left_paddle_turning: bool,
|
||||
pub right_paddle_turning: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1a]
|
||||
pub struct PickItem {
|
||||
pub slot_to_use: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1b]
|
||||
pub struct PlaceRecipe<'a> {
|
||||
pub window_id: i8,
|
||||
pub recipe: Ident<&'a str>,
|
||||
pub make_all: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1c]
|
||||
pub enum PlayerAbilitiesC2s {
|
||||
#[tag = 0b00]
|
||||
StopFlying,
|
||||
#[tag = 0b10]
|
||||
StartFlying,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1d]
|
||||
pub struct PlayerAction {
|
||||
pub status: DiggingStatus,
|
||||
pub location: BlockPos,
|
||||
pub face: BlockFace,
|
||||
pub sequence: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1e]
|
||||
pub struct PlayerCommand {
|
||||
pub entity_id: VarInt,
|
||||
pub action_id: Action,
|
||||
pub jump_boost: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1f]
|
||||
pub struct PlayerInput {
|
||||
pub sideways: f32,
|
||||
pub forward: f32,
|
||||
pub flags: PlayerInputFlags,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x20]
|
||||
pub struct PongPlay {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x21]
|
||||
pub struct ChangeRecipeBookSettings {
|
||||
pub book_id: RecipeBookId,
|
||||
pub book_open: bool,
|
||||
pub filter_active: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x22]
|
||||
pub struct SetSeenRecipe<'a> {
|
||||
pub recipe_id: Ident<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x23]
|
||||
pub struct RenameItem<'a> {
|
||||
pub item_name: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x24]
|
||||
pub enum ResourcePackC2s {
|
||||
SuccessfullyLoaded,
|
||||
Declined,
|
||||
FailedDownload,
|
||||
Accepted,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x25]
|
||||
pub enum SeenAdvancements<'a> {
|
||||
OpenedTab { tab_id: Ident<&'a str> },
|
||||
ClosedScreen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x26]
|
||||
pub struct SelectTrade {
|
||||
pub selected_slot: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x27]
|
||||
pub struct SetBeaconEffect {
|
||||
pub primary_effect: Option<VarInt>,
|
||||
pub secondary_effect: Option<VarInt>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x28]
|
||||
pub struct SetHeldItemC2s {
|
||||
pub slot: i16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x29]
|
||||
pub struct ProgramCommandBlock<'a> {
|
||||
pub location: BlockPos,
|
||||
pub command: &'a str,
|
||||
pub mode: CommandBlockMode,
|
||||
pub flags: CommandBlockFlags,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2a]
|
||||
pub struct ProgramCommandBlockMinecart<'a> {
|
||||
pub entity_id: VarInt,
|
||||
pub command: &'a str,
|
||||
pub track_output: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2b]
|
||||
pub struct SetCreativeModeSlot {
|
||||
pub slot: i16,
|
||||
pub clicked_item: Option<ItemStack>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2c]
|
||||
pub struct ProgramJigsawBlock<'a> {
|
||||
pub location: BlockPos,
|
||||
pub name: Ident<&'a str>,
|
||||
pub target: Ident<&'a str>,
|
||||
pub pool: Ident<&'a str>,
|
||||
pub final_state: &'a str,
|
||||
pub joint_type: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2d]
|
||||
pub struct ProgramStructureBlock<'a> {
|
||||
pub location: BlockPos,
|
||||
pub action: StructureBlockAction,
|
||||
pub mode: StructureBlockMode,
|
||||
pub name: &'a str,
|
||||
pub offset_xyz: [i8; 3],
|
||||
pub size_xyz: [i8; 3],
|
||||
pub mirror: StructureBlockMirror,
|
||||
pub rotation: StructureBlockRotation,
|
||||
pub metadata: &'a str,
|
||||
pub integrity: f32,
|
||||
pub seed: VarLong,
|
||||
pub flags: StructureBlockFlags,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2e]
|
||||
pub struct UpdateSign<'a> {
|
||||
pub location: BlockPos,
|
||||
pub lines: [&'a str; 4],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2f]
|
||||
pub struct SwingArm(pub Hand);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x30]
|
||||
pub struct TeleportToEntity {
|
||||
pub target: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x31]
|
||||
pub struct UseItemOn {
|
||||
pub hand: Hand,
|
||||
pub location: BlockPos,
|
||||
pub face: BlockFace,
|
||||
pub cursor_pos: [f32; 3],
|
||||
pub head_inside_block: bool,
|
||||
pub sequence: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x32]
|
||||
pub struct UseItem {
|
||||
pub hand: Hand,
|
||||
pub sequence: VarInt,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
C2sPlayPacket<'a> {
|
||||
ConfirmTeleport,
|
||||
QueryBlockEntityTag,
|
||||
ChangeDifficulty,
|
||||
MessageAcknowledgmentC2s<'a>,
|
||||
ChatCommand<'a>,
|
||||
ChatMessage<'a>,
|
||||
ChatPreviewC2s,
|
||||
ClientCommand,
|
||||
ClientInformation<'a>,
|
||||
CommandSuggestionsRequest<'a>,
|
||||
ClickContainerButton,
|
||||
ClickContainer,
|
||||
CloseContainerC2s,
|
||||
PluginMessageC2s<'a>,
|
||||
EditBook<'a>,
|
||||
QueryEntityTag,
|
||||
Interact,
|
||||
JigsawGenerate,
|
||||
KeepAliveC2s,
|
||||
LockDifficulty,
|
||||
SetPlayerPosition,
|
||||
SetPlayerPositionAndRotation,
|
||||
SetPlayerRotation,
|
||||
SetPlayerOnGround,
|
||||
MoveVehicleC2s,
|
||||
PaddleBoat,
|
||||
PickItem,
|
||||
PlaceRecipe<'a>,
|
||||
PlayerAbilitiesC2s,
|
||||
PlayerAction,
|
||||
PlayerCommand,
|
||||
PlayerInput,
|
||||
PongPlay,
|
||||
ChangeRecipeBookSettings,
|
||||
SetSeenRecipe<'a>,
|
||||
RenameItem<'a>,
|
||||
ResourcePackC2s,
|
||||
SeenAdvancements<'a>,
|
||||
SelectTrade,
|
||||
SetBeaconEffect,
|
||||
SetHeldItemC2s,
|
||||
ProgramCommandBlock<'a>,
|
||||
ProgramCommandBlockMinecart<'a>,
|
||||
SetCreativeModeSlot,
|
||||
ProgramJigsawBlock<'a>,
|
||||
ProgramStructureBlock<'a>,
|
||||
UpdateSign<'a>,
|
||||
SwingArm,
|
||||
TeleportToEntity,
|
||||
UseItemOn,
|
||||
UseItem
|
||||
}
|
||||
}
|
||||
}
|
695
valence_protocol/src/packets/s2c.rs
Normal file
695
valence_protocol/src/packets/s2c.rs
Normal file
|
@ -0,0 +1,695 @@
|
|||
use uuid::Uuid;
|
||||
use valence_derive::{Decode, Encode, Packet};
|
||||
use valence_nbt::Compound;
|
||||
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::byte_angle::ByteAngle;
|
||||
use crate::ident::Ident;
|
||||
use crate::item::ItemStack;
|
||||
use crate::raw_bytes::RawBytes;
|
||||
use crate::text::Text;
|
||||
use crate::types::{
|
||||
AttributeProperty, BossBarAction, ChunkDataBlockEntity, DeathLocation, Difficulty, GameMode,
|
||||
GameStateChangeReason, PlayerInfoAddPlayer, SignedProperty, SoundCategory,
|
||||
SyncPlayerPosLookFlags,
|
||||
};
|
||||
use crate::username::Username;
|
||||
use crate::var_int::VarInt;
|
||||
use crate::var_long::VarLong;
|
||||
|
||||
pub mod status {
|
||||
use super::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct StatusResponse<'a> {
|
||||
pub json: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x01]
|
||||
pub struct PingResponse {
|
||||
pub payload: u64,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
S2cStatusPacket<'a> {
|
||||
StatusResponse<'a>,
|
||||
PingResponse,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod login {
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct DisconnectLogin {
|
||||
pub reason: Text,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x01]
|
||||
pub struct EncryptionRequest<'a> {
|
||||
pub server_id: &'a str,
|
||||
pub public_key: &'a [u8],
|
||||
pub verify_token: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x02]
|
||||
pub struct LoginSuccess<'a> {
|
||||
pub uuid: Uuid,
|
||||
pub username: Username<&'a str>,
|
||||
pub properties: Vec<SignedProperty<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x03]
|
||||
pub struct SetCompression {
|
||||
pub threshold: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x04]
|
||||
pub struct LoginPluginRequest<'a> {
|
||||
pub message_id: VarInt,
|
||||
pub channel: Ident<&'a str>,
|
||||
pub data: RawBytes<'a>,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
S2cLoginPacket<'a> {
|
||||
DisconnectLogin,
|
||||
EncryptionRequest<'a>,
|
||||
LoginSuccess<'a>,
|
||||
SetCompression,
|
||||
LoginPluginRequest<'a>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod play {
|
||||
use super::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x00]
|
||||
pub struct SpawnEntity {
|
||||
pub entity_id: VarInt,
|
||||
pub object_uuid: Uuid,
|
||||
// TODO: EntityKind type?
|
||||
pub kind: VarInt,
|
||||
pub position: [f64; 3],
|
||||
pub pitch: ByteAngle,
|
||||
pub yaw: ByteAngle,
|
||||
pub head_yaw: ByteAngle,
|
||||
pub data: VarInt,
|
||||
pub velocity: [i16; 3],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x01]
|
||||
pub struct SpawnExperienceOrb {
|
||||
pub entity_id: VarInt,
|
||||
pub position: [f64; 3],
|
||||
pub count: i16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x02]
|
||||
pub struct SpawnPlayer {
|
||||
pub entity_id: VarInt,
|
||||
pub player_uuid: Uuid,
|
||||
pub position: [f64; 3],
|
||||
pub yaw: ByteAngle,
|
||||
pub pitch: ByteAngle,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x03]
|
||||
pub struct EntityAnimationS2c {
|
||||
pub entity_id: VarInt,
|
||||
pub animation: u8, // TODO: use Animation enum.
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x05]
|
||||
pub struct AcknowledgeBlockChange {
|
||||
pub sequence: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x06]
|
||||
pub struct SetBlockDestroyStage {
|
||||
pub entity_id: VarInt,
|
||||
pub location: BlockPos,
|
||||
pub destroy_stage: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x07]
|
||||
pub struct BlockEntityData {
|
||||
pub location: BlockPos,
|
||||
// TODO: BlockEntityKind enum?
|
||||
pub kind: VarInt,
|
||||
pub data: Compound,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x09]
|
||||
pub struct BlockUpdate {
|
||||
pub location: BlockPos,
|
||||
pub block_id: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0a]
|
||||
pub struct BossBar {
|
||||
pub id: Uuid,
|
||||
pub action: BossBarAction,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0b]
|
||||
pub struct SetDifficulty {
|
||||
pub difficulty: Difficulty,
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x0d]
|
||||
pub struct ClearTitles {
|
||||
pub reset: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x11]
|
||||
pub struct SetContainerContent {
|
||||
pub window_id: u8,
|
||||
pub state_id: VarInt,
|
||||
pub slots: Vec<Option<ItemStack>>,
|
||||
pub carried_item: Option<ItemStack>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x12]
|
||||
pub struct SetContainerProperty {
|
||||
pub window_id: u8,
|
||||
pub property: i16,
|
||||
pub value: i16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x13]
|
||||
pub struct SetContainerSlot {
|
||||
pub window_id: i8,
|
||||
pub state_id: VarInt,
|
||||
pub slot_idx: i16,
|
||||
pub slot_data: Option<ItemStack>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x14]
|
||||
pub struct SetCooldown {
|
||||
pub item_id: VarInt,
|
||||
pub cooldown_ticks: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x16]
|
||||
pub struct PluginMessageS2c<'a> {
|
||||
pub channel: Ident<&'a str>,
|
||||
pub data: RawBytes<'a>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x17]
|
||||
pub struct CustomSoundEffect<'a> {
|
||||
pub name: Ident<&'a str>,
|
||||
pub category: SoundCategory,
|
||||
pub position: [i32; 3],
|
||||
pub volume: f32,
|
||||
pub pitch: f32,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x19]
|
||||
pub struct DisconnectPlay {
|
||||
pub reason: Text,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1a]
|
||||
pub struct EntityEvent {
|
||||
pub entity_id: i32,
|
||||
pub entity_status: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1c]
|
||||
pub struct UnloadChunk {
|
||||
pub chunk_x: i32,
|
||||
pub chunk_z: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1d]
|
||||
pub struct GameEvent {
|
||||
pub reason: GameStateChangeReason,
|
||||
pub value: f32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x1f]
|
||||
pub struct WorldBorderInitialize {
|
||||
pub x: f64,
|
||||
pub z: f64,
|
||||
pub old_diameter: f64,
|
||||
pub new_diameter: f64,
|
||||
pub speed: VarLong,
|
||||
pub portal_teleport_boundary: VarInt,
|
||||
pub warning_blocks: VarInt,
|
||||
pub warning_time: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x20]
|
||||
pub struct KeepAliveS2c {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x21]
|
||||
pub struct ChunkDataAndUpdateLight<'a> {
|
||||
pub chunk_x: i32,
|
||||
pub chunk_z: i32,
|
||||
pub heightmaps: Compound,
|
||||
pub blocks_and_biomes: &'a [u8],
|
||||
pub block_entities: Vec<ChunkDataBlockEntity>,
|
||||
pub trust_edges: bool,
|
||||
pub sky_light_mask: Vec<u64>,
|
||||
pub block_light_mask: Vec<u64>,
|
||||
pub empty_sky_light_mask: Vec<u64>,
|
||||
pub empty_block_light_mask: Vec<u64>,
|
||||
pub sky_light_arrays: Vec<(VarInt, [u8; 2048])>,
|
||||
pub block_light_arrays: Vec<(VarInt, [u8; 2048])>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x23]
|
||||
pub struct ParticleS2c<'a> {
|
||||
pub particle_id: VarInt,
|
||||
pub long_distance: bool,
|
||||
pub position: [f64; 3],
|
||||
pub offset: [f32; 3],
|
||||
pub max_speed: f32,
|
||||
pub count: i32,
|
||||
pub data: RawBytes<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x25]
|
||||
pub struct LoginPlay<'a> {
|
||||
pub entity_id: i32,
|
||||
pub is_hardcore: bool,
|
||||
pub game_mode: GameMode,
|
||||
/// Same values as `game_mode` but with -1 to indicate no previous.
|
||||
pub previous_game_mode: i8,
|
||||
pub dimension_names: Vec<Ident<&'a str>>,
|
||||
pub registry_codec: Compound,
|
||||
pub dimension_type_name: Ident<&'a str>,
|
||||
pub dimension_name: Ident<&'a str>,
|
||||
pub hashed_seed: i64,
|
||||
pub max_players: VarInt,
|
||||
pub view_distance: VarInt,
|
||||
pub simulation_distance: VarInt,
|
||||
pub reduced_debug_info: bool,
|
||||
pub enable_respawn_screen: bool,
|
||||
pub is_debug: bool,
|
||||
pub is_flat: bool,
|
||||
pub last_death_location: Option<DeathLocation<'a>>,
|
||||
}
|
||||
|
||||
// TODO: remove this.
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x25]
|
||||
pub struct LoginPlayOwned {
|
||||
pub entity_id: i32,
|
||||
pub is_hardcore: bool,
|
||||
pub game_mode: GameMode,
|
||||
pub previous_game_mode: i8,
|
||||
pub dimension_names: Vec<Ident<String>>,
|
||||
pub registry_codec: Compound,
|
||||
pub dimension_type_name: Ident<String>,
|
||||
pub dimension_name: Ident<String>,
|
||||
pub hashed_seed: i64,
|
||||
pub max_players: VarInt,
|
||||
pub view_distance: VarInt,
|
||||
pub simulation_distance: VarInt,
|
||||
pub reduced_debug_info: bool,
|
||||
pub enable_respawn_screen: bool,
|
||||
pub is_debug: bool,
|
||||
pub is_flat: bool,
|
||||
pub last_death_location: Option<(Ident<String>, BlockPos)>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x28]
|
||||
pub struct UpdateEntityPosition {
|
||||
pub entity_id: VarInt,
|
||||
pub delta: [i16; 3],
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x29]
|
||||
pub struct UpdateEntityPositionAndRotation {
|
||||
pub entity_id: VarInt,
|
||||
pub delta: [i16; 3],
|
||||
pub yaw: ByteAngle,
|
||||
pub pitch: ByteAngle,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2a]
|
||||
pub struct UpdateEntityRotation {
|
||||
pub entity_id: VarInt,
|
||||
pub yaw: ByteAngle,
|
||||
pub pitch: ByteAngle,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x2d]
|
||||
pub struct OpenScreen {
|
||||
pub window_id: VarInt,
|
||||
pub window_type: VarInt,
|
||||
pub window_title: Text,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x33]
|
||||
pub struct PlayerChatMessage<'a> {
|
||||
// TODO: A _lot_ of fields
|
||||
pub data: RawBytes<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x36]
|
||||
pub struct CombatDeath {
|
||||
pub player_id: VarInt,
|
||||
/// Killer's entity ID, -1 if no killer
|
||||
pub entity_id: i32,
|
||||
pub message: Text,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x37]
|
||||
pub enum PlayerInfo<'a> {
|
||||
AddPlayer(Vec<PlayerInfoAddPlayer<'a>>),
|
||||
UpdateGameMode(Vec<(Uuid, GameMode)>),
|
||||
UpdateLatency(Vec<(Uuid, VarInt)>),
|
||||
UpdateDisplayName(Vec<(Uuid, Option<Text>)>),
|
||||
RemovePlayer(Vec<Uuid>),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x39]
|
||||
pub struct SynchronizePlayerPosition {
|
||||
pub position: [f64; 3],
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub flags: SyncPlayerPosLookFlags,
|
||||
pub teleport_id: VarInt,
|
||||
pub dismount_vehicle: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x3b]
|
||||
pub struct RemoveEntities {
|
||||
pub entity_ids: Vec<VarInt>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x3d]
|
||||
pub struct ResourcePackS2c<'a> {
|
||||
pub url: &'a str,
|
||||
pub hash: &'a str,
|
||||
pub forced: bool,
|
||||
pub prompt_message: Option<Text>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x3e]
|
||||
pub struct Respawn<'a> {
|
||||
pub dimension_type_name: Ident<&'a str>,
|
||||
pub dimension_name: Ident<&'a str>,
|
||||
pub hashed_seed: u64,
|
||||
pub game_mode: GameMode,
|
||||
pub previous_game_mode: i8,
|
||||
pub is_debug: bool,
|
||||
pub is_flat: bool,
|
||||
pub copy_metadata: bool,
|
||||
pub last_death_location: Option<DeathLocation<'a>>,
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x3e]
|
||||
pub struct RespawnOwned {
|
||||
pub dimension_type_name: Ident<String>,
|
||||
pub dimension_name: Ident<String>,
|
||||
pub hashed_seed: u64,
|
||||
pub game_mode: GameMode,
|
||||
pub previous_game_mode: i8,
|
||||
pub is_debug: bool,
|
||||
pub is_flat: bool,
|
||||
pub copy_metadata: bool,
|
||||
pub last_death_location: Option<(Ident<String>, BlockPos)>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x3f]
|
||||
pub struct SetHeadRotation {
|
||||
pub entity_id: VarInt,
|
||||
pub head_yaw: ByteAngle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x40]
|
||||
pub struct UpdateSectionBlocks {
|
||||
pub chunk_section_position: i64,
|
||||
pub invert_trust_edges: bool,
|
||||
pub blocks: Vec<VarLong>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x43]
|
||||
pub struct SetActionBarText(pub Text);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x4a]
|
||||
pub struct SetHeldItemS2c {
|
||||
pub slot: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x4b]
|
||||
pub struct SetCenterChunk {
|
||||
pub chunk_x: VarInt,
|
||||
pub chunk_z: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x4c]
|
||||
pub struct SetRenderDistance(pub VarInt);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x4d]
|
||||
pub struct SetDefaultSpawnPosition {
|
||||
pub location: BlockPos,
|
||||
pub angle: f32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x50]
|
||||
pub struct SetEntityMetadata<'a> {
|
||||
pub entity_id: VarInt,
|
||||
pub metadata: RawBytes<'a>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x52]
|
||||
pub struct SetEntityVelocity {
|
||||
pub entity_id: VarInt,
|
||||
pub velocity: [i16; 3],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x54]
|
||||
pub struct SetExperience {
|
||||
pub bar: f32,
|
||||
pub level: VarInt,
|
||||
pub total_xp: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x55]
|
||||
pub struct SetHealth {
|
||||
pub health: f32,
|
||||
pub food: VarInt,
|
||||
pub food_saturation: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x5b]
|
||||
pub struct SetSubtitleText(pub Text);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x5c]
|
||||
pub struct UpdateTime {
|
||||
/// The age of the world in 1/20ths of a second.
|
||||
pub 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.
|
||||
pub time_of_day: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x5d]
|
||||
pub struct SetTitleText(pub Text);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x5e]
|
||||
pub struct SetTitleAnimationTimes {
|
||||
/// Ticks to spend fading in.
|
||||
pub fade_in: i32,
|
||||
/// Ticks to keep the title displayed.
|
||||
pub stay: i32,
|
||||
/// Ticks to spend fading out.
|
||||
pub fade_out: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x5f]
|
||||
pub struct EntitySoundEffect {
|
||||
pub id: VarInt,
|
||||
pub category: SoundCategory,
|
||||
pub entity_id: VarInt,
|
||||
pub volume: f32,
|
||||
pub pitch: f32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x60]
|
||||
pub struct SoundEffect {
|
||||
pub id: VarInt,
|
||||
pub category: SoundCategory,
|
||||
pub position: [i32; 3],
|
||||
pub volume: f32,
|
||||
pub pitch: f32,
|
||||
pub seed: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x62]
|
||||
pub struct SystemChatMessage {
|
||||
pub chat: Text,
|
||||
/// Index into the chat type registry.
|
||||
pub kind: VarInt,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x63]
|
||||
pub struct SetTabListHeaderAndFooter {
|
||||
pub header: Text,
|
||||
pub footer: Text,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x66]
|
||||
pub struct TeleportEntity {
|
||||
pub entity_id: VarInt,
|
||||
pub position: [f64; 3],
|
||||
pub yaw: ByteAngle,
|
||||
pub pitch: ByteAngle,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||
#[packet_id = 0x68]
|
||||
pub struct UpdateAttributes<'a> {
|
||||
pub entity_id: VarInt,
|
||||
pub properties: Vec<AttributeProperty<'a>>,
|
||||
}
|
||||
|
||||
packet_enum! {
|
||||
#[derive(Clone, Debug)]
|
||||
S2cPlayPacket<'a> {
|
||||
SpawnEntity,
|
||||
SpawnExperienceOrb,
|
||||
SpawnPlayer,
|
||||
EntityAnimationS2c,
|
||||
AcknowledgeBlockChange,
|
||||
SetBlockDestroyStage,
|
||||
BlockEntityData,
|
||||
BlockUpdate,
|
||||
BossBar,
|
||||
SetDifficulty,
|
||||
ClearTitles,
|
||||
SetContainerContent,
|
||||
SetContainerProperty,
|
||||
SetContainerSlot,
|
||||
SetCooldown,
|
||||
PluginMessageS2c<'a>,
|
||||
CustomSoundEffect<'a>,
|
||||
DisconnectPlay,
|
||||
EntityEvent,
|
||||
UnloadChunk,
|
||||
GameEvent,
|
||||
WorldBorderInitialize,
|
||||
KeepAliveS2c,
|
||||
ChunkDataAndUpdateLight<'a>,
|
||||
ParticleS2c<'a>,
|
||||
LoginPlay<'a>,
|
||||
UpdateEntityPosition,
|
||||
UpdateEntityPositionAndRotation,
|
||||
UpdateEntityRotation,
|
||||
OpenScreen,
|
||||
PlayerChatMessage<'a>,
|
||||
CombatDeath,
|
||||
PlayerInfo<'a>,
|
||||
SynchronizePlayerPosition,
|
||||
RemoveEntities,
|
||||
ResourcePackS2c<'a>,
|
||||
Respawn<'a>,
|
||||
SetHeadRotation,
|
||||
UpdateSectionBlocks,
|
||||
SetActionBarText,
|
||||
SetHeldItemS2c,
|
||||
SetCenterChunk,
|
||||
SetRenderDistance,
|
||||
SetDefaultSpawnPosition,
|
||||
SetEntityMetadata<'a>,
|
||||
SetEntityVelocity,
|
||||
SetExperience,
|
||||
SetHealth,
|
||||
SetSubtitleText,
|
||||
UpdateTime,
|
||||
SetTitleText,
|
||||
SetTitleAnimationTimes,
|
||||
EntitySoundEffect,
|
||||
SoundEffect,
|
||||
SystemChatMessage,
|
||||
SetTabListHeaderAndFooter,
|
||||
TeleportEntity,
|
||||
UpdateAttributes<'a>,
|
||||
}
|
||||
}
|
||||
}
|
32
valence_protocol/src/raw_bytes.rs
Normal file
32
valence_protocol/src/raw_bytes.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use std::io::Write;
|
||||
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
/// While [encoding], the contained slice is written directly to the output
|
||||
/// without any metadata.
|
||||
///
|
||||
/// While [decoding], the remainder of the input is returned as the contained
|
||||
/// slice. The input will be at the EOF state after this is finished.
|
||||
///
|
||||
/// [encoding]: Encode
|
||||
/// [decoding]: Decode
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct RawBytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl Encode for RawBytes<'_> {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
Ok(w.write_all(self.0)?)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decode<'a> for RawBytes<'a> {
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
let slice = *r;
|
||||
*r = &[];
|
||||
Ok(Self(slice))
|
||||
}
|
||||
}
|
|
@ -7,8 +7,10 @@ use std::io::Write;
|
|||
use serde::de::Visitor;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::byte_counter::ByteCounter;
|
||||
use crate::ident::Ident;
|
||||
use crate::protocol::{BoundedString, Decode, Encode};
|
||||
use crate::var_int::VarInt;
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
/// Represents formatted text in Minecraft's JSON text format.
|
||||
///
|
||||
|
@ -26,7 +28,7 @@ use crate::protocol::{BoundedString, Decode, Encode};
|
|||
///
|
||||
/// With [`TextFormat`] in scope, you can write the following:
|
||||
/// ```
|
||||
/// use valence::text::{Color, Text, TextFormat};
|
||||
/// use valence_protocol::text::{Color, Text, TextFormat};
|
||||
///
|
||||
/// let txt = "The text is ".into_text()
|
||||
/// + "Red".color(Color::RED)
|
||||
|
@ -452,21 +454,24 @@ impl fmt::Display for Text {
|
|||
}
|
||||
}
|
||||
|
||||
/// Encode implementation for Text is not very fast. Beware.
|
||||
impl Encode for Text {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
BoundedString::<0, 262144>(serde_json::to_string(self)?).encode(w)
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
serde_json::to_string(self)?.encode(w)
|
||||
}
|
||||
|
||||
fn encoded_len(&self) -> usize {
|
||||
// TODO: This is obviously not ideal. This will be fixed later.
|
||||
serde_json::to_string(self).map_or(0, |s| s.encoded_len())
|
||||
let mut counter = ByteCounter::new();
|
||||
let _ = serde_json::to_writer(&mut counter, self);
|
||||
|
||||
VarInt(counter.0.try_into().unwrap_or(i32::MAX)).encoded_len() + counter.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Text {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let string = BoundedString::<0, 262144>::decode(r)?;
|
||||
Ok(serde_json::from_str(&string.0)?)
|
||||
impl Decode<'_> for Text {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let string = <&str>::decode(r)?;
|
||||
Ok(serde_json::from_str(string)?)
|
||||
}
|
||||
}
|
||||
|
369
valence_protocol/src/types.rs
Normal file
369
valence_protocol/src/types.rs
Normal file
|
@ -0,0 +1,369 @@
|
|||
//! Miscellaneous type definitions used in packets.
|
||||
|
||||
use bitfield_struct::bitfield;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use valence_nbt::Compound;
|
||||
use valence_protocol::text::Text;
|
||||
|
||||
use crate::__private::VarInt;
|
||||
use crate::block_pos::BlockPos;
|
||||
use crate::ident::Ident;
|
||||
use crate::{Decode, Encode};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub enum HandshakeNextState {
|
||||
#[tag = 1]
|
||||
Status,
|
||||
#[tag = 2]
|
||||
Login,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct PublicKeyData<'a> {
|
||||
pub timestamp: u64,
|
||||
pub public_key: &'a [u8],
|
||||
pub signature: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode)]
|
||||
pub enum MsgSigOrVerifyToken<'a> {
|
||||
MsgSig { salt: u64, sig: &'a [u8] },
|
||||
VerifyToken(&'a [u8]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode)]
|
||||
pub struct MessageAcknowledgment<'a> {
|
||||
pub last_seen: Vec<MessageAcknowledgmentEntry<'a>>,
|
||||
pub last_received: Option<MessageAcknowledgmentEntry<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode)]
|
||||
pub struct MessageAcknowledgmentEntry<'a> {
|
||||
pub profile_id: Uuid,
|
||||
pub signature: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Encode, Decode)]
|
||||
pub struct CommandArgumentSignature<'a> {
|
||||
pub argument_name: &'a str,
|
||||
pub signature: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum ChatMode {
|
||||
Enabled,
|
||||
CommandsOnly,
|
||||
Hidden,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)]
|
||||
pub enum MainHand {
|
||||
Left,
|
||||
#[default]
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub enum ClickContainerMode {
|
||||
Click,
|
||||
ShiftClick,
|
||||
Hotbar,
|
||||
CreativeMiddleClick,
|
||||
DropKey,
|
||||
Drag,
|
||||
DoubleClick,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum Hand {
|
||||
Main,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)]
|
||||
pub enum EntityInteraction {
|
||||
Interact(Hand),
|
||||
Attack,
|
||||
InteractAt { target: [f32; 3], hand: Hand },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum DiggingStatus {
|
||||
StartedDigging,
|
||||
CancelledDigging,
|
||||
FinishedDigging,
|
||||
DropItemStack,
|
||||
DropItem,
|
||||
ShootArrowOrFinishEating,
|
||||
SwapItemInHand,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum Action {
|
||||
StartSneaking,
|
||||
StopSneaking,
|
||||
LeaveBed,
|
||||
StartSprinting,
|
||||
StopSprinting,
|
||||
StartJumpWithHorse,
|
||||
StopJumpWithHorse,
|
||||
OpenHorseInventory,
|
||||
StartFlyingWithElytra,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum RecipeBookId {
|
||||
Crafting,
|
||||
Furnace,
|
||||
BlastFurnace,
|
||||
Smoker,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum CommandBlockMode {
|
||||
Sequence,
|
||||
Auto,
|
||||
Redstone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum StructureBlockAction {
|
||||
UpdateData,
|
||||
SaveStructure,
|
||||
LoadStructure,
|
||||
DetectSize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum StructureBlockMode {
|
||||
Save,
|
||||
Load,
|
||||
Corner,
|
||||
Data,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum StructureBlockMirror {
|
||||
None,
|
||||
LeftRight,
|
||||
FrontBack,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum StructureBlockRotation {
|
||||
None,
|
||||
Clockwise90,
|
||||
Clockwise180,
|
||||
Counterclockwise90,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)]
|
||||
pub struct SignedProperty<'a> {
|
||||
pub name: &'a str,
|
||||
pub value: &'a str,
|
||||
pub signature: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)]
|
||||
pub struct SignedPropertyOwned {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum Animation {
|
||||
SwingMainArm,
|
||||
TakeDamage,
|
||||
LeaveBed,
|
||||
SwingOffhand,
|
||||
CriticalEffect,
|
||||
MagicCriticalEffect,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||
pub enum BossBarAction {
|
||||
Add {
|
||||
title: Text,
|
||||
health: f32,
|
||||
color: BossBarColor,
|
||||
division: BossBarDivision,
|
||||
flags: BossBarFlags,
|
||||
},
|
||||
Remove,
|
||||
UpdateHealth(f32),
|
||||
UpdateTitle(Text),
|
||||
UpdateStyle(BossBarColor, BossBarDivision),
|
||||
UpdateFlags(BossBarFlags),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum BossBarColor {
|
||||
Pink,
|
||||
Blue,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Purple,
|
||||
White,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum BossBarDivision {
|
||||
NoDivision,
|
||||
SixNotches,
|
||||
TenNotches,
|
||||
TwelveNotches,
|
||||
TwentyNotches,
|
||||
}
|
||||
|
||||
#[bitfield(u8)]
|
||||
#[derive(PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct BossBarFlags {
|
||||
pub darken_sky: bool,
|
||||
pub dragon_bar: bool,
|
||||
pub create_fog: bool,
|
||||
#[bits(5)]
|
||||
_pad: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum Difficulty {
|
||||
Peaceful,
|
||||
Easy,
|
||||
Normal,
|
||||
Hard,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum SoundCategory {
|
||||
Master,
|
||||
Music,
|
||||
Record,
|
||||
Weather,
|
||||
Block,
|
||||
Hostile,
|
||||
Neutral,
|
||||
Player,
|
||||
Ambient,
|
||||
Voice,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum GameStateChangeReason {
|
||||
NoRespawnBlockAvailable,
|
||||
EndRaining,
|
||||
BeginRaining,
|
||||
ChangeGameMode,
|
||||
WinGame,
|
||||
DemoEvent,
|
||||
ArrowHitPlayer,
|
||||
RainLevelChange,
|
||||
ThunderLevelChange,
|
||||
PlayPufferfishStingSound,
|
||||
PlayElderGuardianMobAppearance,
|
||||
EnableRespawnScreen,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||
pub struct ChunkDataBlockEntity {
|
||||
pub packed_xz: i8,
|
||||
pub y: i16,
|
||||
// TODO: block entity kind?
|
||||
pub kind: VarInt,
|
||||
pub data: Compound,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub enum GameMode {
|
||||
Survival,
|
||||
Creative,
|
||||
Adventure,
|
||||
Spectator,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct DeathLocation<'a> {
|
||||
pub dimension_name: Ident<&'a str>,
|
||||
pub position: BlockPos,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||
pub struct AttributeProperty<'a> {
|
||||
pub key: Ident<&'a str>,
|
||||
pub value: f64,
|
||||
pub modifiers: Vec<AttributeModifier>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||
pub struct AttributeModifier {
|
||||
pub uuid: Uuid,
|
||||
pub amount: f64,
|
||||
pub operation: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||
pub struct PlayerInfoAddPlayer<'a> {
|
||||
pub uuid: Uuid,
|
||||
pub username: &'a str,
|
||||
pub properties: Vec<SignedProperty<'a>>,
|
||||
pub game_mode: GameMode,
|
||||
pub ping: VarInt,
|
||||
pub display_name: Option<Text>,
|
||||
pub sig_data: Option<PublicKeyData<'a>>,
|
||||
}
|
||||
|
||||
#[bitfield(u8)]
|
||||
#[derive(PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct DisplayedSkinParts {
|
||||
pub cape: bool,
|
||||
pub jacket: bool,
|
||||
pub left_sleeve: bool,
|
||||
pub right_sleeve: bool,
|
||||
pub left_pants_leg: bool,
|
||||
pub right_pants_leg: bool,
|
||||
pub hat: bool,
|
||||
_pad: bool,
|
||||
}
|
||||
|
||||
#[bitfield(u8)]
|
||||
#[derive(PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct PlayerInputFlags {
|
||||
pub jump: bool,
|
||||
pub unmount: bool,
|
||||
#[bits(6)]
|
||||
_pad: u8,
|
||||
}
|
||||
|
||||
#[bitfield(u8)]
|
||||
#[derive(PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct CommandBlockFlags {
|
||||
pub track_output: bool,
|
||||
pub conditional: bool,
|
||||
pub automatic: bool,
|
||||
#[bits(5)]
|
||||
_pad: u8,
|
||||
}
|
||||
|
||||
#[bitfield(u8)]
|
||||
#[derive(PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct StructureBlockFlags {
|
||||
pub ignore_entities: bool,
|
||||
pub show_air: bool,
|
||||
pub show_bounding_box: bool,
|
||||
#[bits(5)]
|
||||
_pad: u8,
|
||||
}
|
||||
|
||||
#[bitfield(u8)]
|
||||
#[derive(PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct SyncPlayerPosLookFlags {
|
||||
pub x: bool,
|
||||
pub y: bool,
|
||||
pub z: bool,
|
||||
pub y_rot: bool,
|
||||
pub x_rot: bool,
|
||||
#[bits(3)]
|
||||
_pad: u8,
|
||||
}
|
|
@ -5,10 +5,11 @@ use std::fmt::Formatter;
|
|||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::de::Error as _;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
/// A newtype wrapper around a string type `S` which guarantees the wrapped
|
||||
/// string meets the criteria for a valid Minecraft username.
|
||||
|
@ -26,7 +27,7 @@ use crate::protocol::{Decode, Encode};
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use valence::prelude::*;
|
||||
/// use valence_protocol::username::Username;
|
||||
///
|
||||
/// assert!(Username::new("00a").is_ok());
|
||||
/// assert!(Username::new("jeb_").is_ok());
|
||||
|
@ -58,6 +59,12 @@ impl<S: AsRef<str>> Username<S> {
|
|||
Username(self.0.as_ref())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> S {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: ?Sized> Username<&'a S> {
|
||||
pub fn to_owned_username(&self) -> Username<S::Owned>
|
||||
where
|
||||
S: ToOwned,
|
||||
|
@ -65,10 +72,6 @@ impl<S: AsRef<str>> Username<S> {
|
|||
{
|
||||
Username(self.0.to_owned())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> S {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> AsRef<str> for Username<S>
|
||||
|
@ -127,7 +130,7 @@ impl<S> Encode for Username<S>
|
|||
where
|
||||
S: Encode,
|
||||
{
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, w: impl Write) -> Result<()> {
|
||||
self.0.encode(w)
|
||||
}
|
||||
|
||||
|
@ -136,12 +139,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S> Decode for Username<S>
|
||||
impl<'a, S> Decode<'a> for Username<S>
|
||||
where
|
||||
S: Decode + AsRef<str> + Send + Sync + 'static,
|
||||
S: Decode<'a> + AsRef<str>,
|
||||
{
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(Username::new(S::decode(r)?)?)
|
||||
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||
Username::new(S::decode(r)?).map_err(|e| anyhow!("{e:#}"))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use anyhow::bail;
|
|||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::{Decode, Encode};
|
||||
|
||||
/// An `i32` encoded with variable length.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
|
@ -15,7 +15,7 @@ impl VarInt {
|
|||
/// written to the Minecraft protocol.
|
||||
pub const MAX_SIZE: usize = 5;
|
||||
|
||||
pub(crate) fn decode_partial(mut r: impl Read) -> Result<i32, VarIntDecodeError> {
|
||||
pub fn decode_partial(mut r: impl Read) -> Result<i32, VarIntDecodeError> {
|
||||
let mut val = 0;
|
||||
for i in 0..Self::MAX_SIZE {
|
||||
let byte = r.read_u8().map_err(|_| VarIntDecodeError::Incomplete)?;
|
||||
|
@ -30,7 +30,7 @@ impl VarInt {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)]
|
||||
pub(crate) enum VarIntDecodeError {
|
||||
pub enum VarIntDecodeError {
|
||||
#[error("incomplete VarInt decode")]
|
||||
Incomplete,
|
||||
#[error("VarInt is too large")]
|
||||
|
@ -38,7 +38,7 @@ pub(crate) enum VarIntDecodeError {
|
|||
}
|
||||
|
||||
impl Encode for VarInt {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
|
||||
let mut val = self.0 as u32;
|
||||
loop {
|
||||
if val & 0b11111111111111111111111110000000 == 0 {
|
||||
|
@ -58,7 +58,7 @@ impl Encode for VarInt {
|
|||
}
|
||||
}
|
||||
|
||||
impl Decode for VarInt {
|
||||
impl Decode<'_> for VarInt {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
let mut val = 0;
|
||||
for i in 0..Self::MAX_SIZE {
|
||||
|
@ -72,24 +72,18 @@ impl Decode for VarInt {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<VarInt> for i32 {
|
||||
fn from(i: VarInt) -> Self {
|
||||
i.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VarInt> for i64 {
|
||||
fn from(i: VarInt) -> Self {
|
||||
i.0 as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for VarInt {
|
||||
fn from(i: i32) -> Self {
|
||||
VarInt(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VarInt> for i32 {
|
||||
fn from(i: VarInt) -> Self {
|
||||
i.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{thread_rng, Rng};
|
|
@ -3,11 +3,11 @@ use std::io::Write;
|
|||
use anyhow::bail;
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::protocol::{Decode, Encode};
|
||||
use crate::{Decode, Encode, Result};
|
||||
|
||||
/// An `i64` encoded with variable length.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct VarLong(pub(crate) i64);
|
||||
pub struct VarLong(pub i64);
|
||||
|
||||
impl VarLong {
|
||||
/// The maximum number of bytes a `VarLong` can occupy when read from and
|
||||
|
@ -16,7 +16,7 @@ impl VarLong {
|
|||
}
|
||||
|
||||
impl Encode for VarLong {
|
||||
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||
fn encode(&self, mut w: impl Write) -> Result<()> {
|
||||
let mut val = self.0 as u64;
|
||||
loop {
|
||||
if val & 0b1111111111111111111111111111111111111111111111111111111110000000 == 0 {
|
||||
|
@ -36,8 +36,8 @@ impl Encode for VarLong {
|
|||
}
|
||||
}
|
||||
|
||||
impl Decode for VarLong {
|
||||
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
impl Decode<'_> for VarLong {
|
||||
fn decode(r: &mut &[u8]) -> Result<Self> {
|
||||
let mut val = 0;
|
||||
for i in 0..Self::MAX_SIZE {
|
||||
let byte = r.read_u8()?;
|
Loading…
Add table
Reference in a new issue