diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cfc7e4..66e0b7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt - - run: cp crates/playground/src/playground.template.rs crates/playground/src/playground.rs + - run: cp tools/playground/src/playground.template.rs tools/playground/src/playground.rs - name: Set up cargo cache uses: actions/cache@v3 continue-on-error: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f704ca..66a8d38 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Playgrounds are meant to provide a quick and minimal environment to test out new To get started with a new playground, copy the template to `playground.rs`. ```bash -cp crates/playground/src/playground.template.rs crates/playground/src/playground.rs +cp tools/playground/src/playground.template.rs tools/playground/src/playground.rs ``` Make your changes to `crates/playground/src/playground.rs`. To run it: diff --git a/Cargo.toml b/Cargo.toml index e4c5970..9f0ba57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,94 @@ [workspace] -members = ["crates/*"] +members = ["crates/*", "tools/*"] exclude = ["rust-mc-bot"] resolver = "2" +[workspace.package] +version = "0.2.0-dev+mc.1.19.4" +edition = "2021" +repository = "https://github.com/valence-rs/valence" +documentation = "https://docs.rs/valence/" +license = "MIT" + +[workspace.dependencies] +aes = "0.7.5" +anyhow = "1.0.70" +approx = "0.5.1" +arrayvec = "0.7.2" +async-trait = "0.1.60" +atty = "0.2.14" +base64 = "0.21.0" +bevy_app = { version = "0.10.1", default-features = false } +bevy_ecs = { version = "0.10.1", default-features = false, features = ["trace"] } +bevy_mod_debugdump = "0.7.0" +bitfield-struct = "0.3.1" +byteorder = "1.4.3" +bytes = "1.2.1" +cesu8 = "1.1.0" +cfb8 = "0.7.1" +clap = { version = "4.0.30", features = ["derive"] } +criterion = "0.4.0" +directories = "5.0.0" +eframe = { version = "0.21.0", default-features = false } +egui = "0.21.0" +enum-map = "2.4.2" +flate2 = "1.0.24" +flume = "0.10.14" +fs_extra = "1.2.0" +glam = "0.23.0" +heck = "0.4.0" +hmac = "0.12.1" +indexmap = "1.9.3" +noise = "0.8.2" +num = "0.4.0" +num-bigint = "0.4.3" +num-integer = "0.1.45" +owo-colors = "3.5.0" +parking_lot = "0.12.1" +paste = "1.0.11" +proc-macro2 = "1.0.56" +quote = "1.0.26" +rand = "0.8.5" +rayon = "1.7.0" +regex = "1.6.0" +reqwest = { version = "0.11.12", default-features = false } +rfd = "0.11.3" +rsa = "0.7.2" +rsa-der = "0.3.0" +rustc-hash = "1.1.0" +serde = "1.0.160" +serde_json = "1.0.96" +sha1 = "0.10.5" +sha2 = "0.10.6" +syn = "1.0.109" # TODO: update this. +syntect = { version = "5.0.0", default-features = false } +tempfile = "3.3.0" +thiserror = "1.0.40" +time = "0.3.17" +tokio = { version = "1.27.0", features = ["full"] } +toml = "0.7.2" +tracing = "0.1.37" +tracing-subscriber = "0.3.16" +url = { version = "2.2.2", features = ["serde"] } +uuid = "1.3.1" +valence_anvil.path = "crates/valence_anvil" +valence_biome.path = "crates/valence_biome" +valence_block.path = "crates/valence_block" +valence_build_utils.path = "crates/valence_build_utils" +valence_client.path = "crates/valence_client" +valence_core_macros.path = "crates/valence_core_macros" +valence_core.path = "crates/valence_core" +valence_dimension.path = "crates/valence_dimension" +valence_entity.path = "crates/valence_entity" +valence_instance.path = "crates/valence_instance" +valence_inventory.path = "crates/valence_inventory" +valence_nbt = { path = "crates/valence_nbt", features = ["uuid"] } +valence_network.path = "crates/valence_network" +valence_player_list.path = "crates/valence_player_list" +valence_registry.path = "crates/valence_registry" +valence.path = "crates/valence" +zip = "0.6.3" + [profile.dev.package."*"] opt-level = 3 diff --git a/README.md b/README.md index 4f3d3ec..7d3ac4c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

@@ -27,6 +27,8 @@ Opinionated features like dynamic scripting, dedicated executables, and vanilla built as optional plugins. This level of modularity is desirable for those looking to build highly custom experiences in Minecraft such as minigame servers. +⚠️ **Valence is still early in development with many features unimplemented or incomplete. Expect to encounter bugs, limitations, and breaking changes.** + # Goals Valence aims to be the following: @@ -45,11 +47,8 @@ Valence aims to be the following: ## Current Status -⚠️ **Valence is still early in development with many features unimplemented or incomplete. Expect to encounter bugs, limitations, and breaking changes.** Here are some noteworthy achievements: - +Here are some noteworthy achievements: - `valence_nbt`: A speedy new library for Minecraft's Named Binary Tag (NBT) format. -- `valence_protocol`: A library for working with Minecraft's protocol. Does not depend on Valence and can be used in - other projects. - Authentication, encryption, and compression - Block states - Chunks diff --git a/crates/dump_schedule/Cargo.toml b/crates/dump_schedule/Cargo.toml deleted file mode 100644 index bef78ae..0000000 --- a/crates/dump_schedule/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "dump_schedule" -version = "0.1.0" -edition = "2021" - -[dependencies] -bevy_mod_debugdump = "0.7.0" -valence = { path = "../valence", version = "0.2.0" } diff --git a/crates/packet_inspector/Cargo.toml b/crates/packet_inspector/Cargo.toml deleted file mode 100644 index cefb1c8..0000000 --- a/crates/packet_inspector/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "packet_inspector" -version = "0.1.0" -edition = "2021" -description = "A simple Minecraft proxy for inspecting packets." - -[features] -default = ["syntax_highlighting"] -syntax_highlighting = ["syntect"] - -[dependencies] -anyhow = "1" -clap = { version = "4.0.30", features = ["derive"] } -regex = "1.6.0" -tokio = { version = "1", features = ["full"] } -tracing-subscriber = "0.3.16" -valence_protocol = { path = "../valence_protocol", version = "0.1.0", features = [ - "compression", -] } - -egui = "0.21.0" -eframe = { version = "0.21.0", default-features = false, features = [ - "default_fonts", # Embed the default egui fonts. - "glow", # Use the glow rendering backend. Alternative: "wgpu". -] } -enum-map = "2.4.2" -syntect = { version = "5", optional = true, default-features = false, features = [ - "default-fancy", -] } -time = { version = "0.3.17", features = ["local-offset"] } -rfd = "0.11" -owo-colors = "3.5.0" -atty = "0.2" -directories = "4.0" -serde = { version = "1.0.152", features = ["derive"] } -toml = "0.7.2" -valence_nbt = { path = "../valence_nbt", version = "0.5.0", features = ["preserve_order"] } diff --git a/crates/playground/src/main.rs b/crates/playground/src/main.rs deleted file mode 100644 index 54ab3c6..0000000 --- a/crates/playground/src/main.rs +++ /dev/null @@ -1,16 +0,0 @@ -use tracing::Level; -use valence::bevy_app::App; - -#[allow(dead_code)] -mod extras; -mod playground; - -fn main() { - tracing_subscriber::fmt() - .with_max_level(Level::DEBUG) - .init(); - - let mut app = App::new(); - playground::build_app(&mut app); - app.run(); -} diff --git a/crates/valence/Cargo.toml b/crates/valence/Cargo.toml index 0646306..7c84c43 100644 --- a/crates/valence/Cargo.toml +++ b/crates/valence/Cargo.toml @@ -1,71 +1,61 @@ [package] name = "valence" -version = "0.2.0+mc1.19.4" -edition = "2021" +version.workspace = true +edition.workspace = true description = "A framework for building Minecraft servers in Rust." -repository = "https://github.com/rj00a/valence" +documentation.workspace = true +repository.workspace = true readme = "README.md" -license = "MIT" +license.workspace = true keywords = ["minecraft", "gamedev", "server", "ecs"] categories = ["game-engines"] -build = "build/main.rs" -authors = ["Ryan Johnson "] + +[features] +default = ["network", "player_list", "inventory", "anvil"] +network = ["dep:valence_network"] +player_list = ["dep:valence_player_list"] +inventory = ["dep:valence_inventory"] +anvil = ["dep:valence_anvil"] [dependencies] -anyhow = "1.0.65" -arrayvec = "0.7.2" -async-trait = "0.1.60" -base64 = "0.21.0" -bevy_app = "0.10" -bevy_ecs = "0.10" -bitfield-struct = "0.3.1" -bytes = "1.2.1" -flume = "0.10.14" -glam = "0.23.0" -hmac = "0.12.1" -num = "0.4.0" -parking_lot = "0.12.1" -paste = "1.0.11" -rand = "0.8.5" -rsa = "0.7.2" -rsa-der = "0.3.0" -rustc-hash = "1.1.0" -serde = { version = "1.0.145", features = ["derive"] } -serde_json = "1.0.85" -sha1 = "0.10.5" -sha2 = "0.10.6" -thiserror = "1.0.35" -tokio = { version = "1.25.0", features = ["full"] } -tracing = "0.1.37" -url = { version = "2.2.2", features = ["serde"] } -uuid = { version = "1.1.2", features = ["serde"] } -valence_nbt = { version = "0.5.0", path = "../valence_nbt", features = [ - "uuid", -] } -valence_protocol = { version = "0.1.0", path = "../valence_protocol", features = [ - "encryption", - "compression", - "glam", -] } - -[dependencies.reqwest] -version = "0.11.12" -default-features = false -# Avoid OpenSSL dependency on Linux. -features = ["rustls-tls", "json"] +bevy_app.workspace = true +bevy_ecs.workspace = true +glam.workspace = true +uuid.workspace = true +valence_nbt.workspace = true +valence_core.workspace = true +valence_registry.workspace = true +valence_block.workspace = true +valence_biome.workspace = true +valence_dimension.workspace = true +valence_entity.workspace = true +valence_instance.workspace = true +valence_client.workspace = true +valence_network = { workspace = true, optional = true } +valence_player_list = { workspace = true, optional = true } +valence_inventory = { workspace = true, optional = true } +valence_anvil = { workspace = true, optional = true } [dev-dependencies] -approx = "0.5.1" -glam = { version = "0.23.0", features = ["approx"] } -noise = "0.8.2" -tracing-subscriber = "0.3.16" +anyhow.workspace = true +bytes.workspace = true +noise.workspace = true +tracing-subscriber.workspace = true +rand.workspace = true +tracing.workspace = true +flume.workspace = true +clap.workspace = true +criterion.workspace = true +fs_extra.workspace = true +tempfile.workspace = true +zip.workspace = true -[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" -rayon = "1.5.3" -num = "0.4.0" +[dev-dependencies.reqwest] +workspace = true +default-features = false +# Avoid OpenSSL dependency on Linux. +features = ["rustls-tls", "blocking", "stream"] + +[[bench]] +name = "main" +harness = false diff --git a/crates/valence/README.md b/crates/valence/README.md new file mode 100644 index 0000000..f9bed2f --- /dev/null +++ b/crates/valence/README.md @@ -0,0 +1,5 @@ +# valence + +The main Valence crate. This acts primarily as a shell to re-export the other crates and consolidate plugins. Users are expected to use the other crates indirectly through this interface. + +Additionally, project examples and whole-server unit tests are stored in `examples/` and `src/tests/` respectively. diff --git a/crates/valence_anvil/benches/world_parsing.rs b/crates/valence/benches/anvil.rs similarity index 93% rename from crates/valence_anvil/benches/world_parsing.rs rename to crates/valence/benches/anvil.rs index 5cadf1a..327bced 100644 --- a/crates/valence_anvil/benches/world_parsing.rs +++ b/crates/valence/benches/anvil.rs @@ -1,18 +1,16 @@ use std::fs::create_dir_all; +use std::hint::black_box; use std::path::{Path, PathBuf}; use anyhow::{ensure, Context}; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::Criterion; use fs_extra::dir::CopyOptions; use reqwest::IntoUrl; +use valence::anvil::AnvilWorld; use valence::instance::Chunk; -use valence_anvil::AnvilWorld; use zip::ZipArchive; -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); - -fn criterion_benchmark(c: &mut Criterion) { +pub fn load(c: &mut Criterion) { let world_dir = get_world_asset( "https://github.com/valence-rs/valence-test-data/archive/refs/heads/asset/sp_world_1.19.2.zip", "1.19.2 benchmark world", @@ -21,7 +19,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut world = AnvilWorld::new(world_dir); - c.bench_function("Load square 10x10", |b| { + c.bench_function("anvil_load_10x10", |b| { b.iter(|| { let world = black_box(&mut world); diff --git a/crates/valence/benches/block.rs b/crates/valence/benches/block.rs new file mode 100644 index 0000000..472c1aa --- /dev/null +++ b/crates/valence/benches/block.rs @@ -0,0 +1,89 @@ +use std::hint::black_box; + +use criterion::Criterion; +use valence::block::{BlockKind, BlockState, PropName, PropValue}; +use valence::item::ItemKind; + +pub fn block(c: &mut Criterion) { + let states = BlockKind::ALL.map(BlockKind::to_state); + + c.bench_function("BlockState::from_kind", |b| { + b.iter(|| { + for kind in black_box(BlockKind::ALL) { + black_box(BlockState::from_kind(kind)); + } + }); + }); + + c.bench_function("BlockState::to_kind", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.to_kind()); + } + }); + }); + + c.bench_function("BlockState::get", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.get(PropName::Note)); + } + }); + }); + + c.bench_function("BlockState::set", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.set(PropName::Note, PropValue::Didgeridoo)); + } + }); + }); + + c.bench_function("BlockState::is_liquid", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.is_liquid()); + } + }); + }); + + c.bench_function("BlockState::is_opaque", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.is_opaque()); + } + }) + }); + + c.bench_function("BlockState::is_replaceable", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.is_replaceable()); + } + }) + }); + + c.bench_function("BlockState::luminance", |b| { + b.iter(|| { + for state in black_box(states) { + black_box(state.luminance()); + } + }) + }); + + c.bench_function("BlockKind::to_item_kind", |b| { + b.iter(|| { + for kind in black_box(BlockKind::ALL) { + black_box(kind.to_item_kind()); + } + }); + }); + + c.bench_function("BlockKind::from_item_kind", |b| { + b.iter(|| { + for kind in black_box(ItemKind::ALL) { + black_box(BlockKind::from_item_kind(kind)); + } + }); + }); +} diff --git a/crates/valence/benches/decode_array.rs b/crates/valence/benches/decode_array.rs new file mode 100644 index 0000000..1f82ae6 --- /dev/null +++ b/crates/valence/benches/decode_array.rs @@ -0,0 +1,27 @@ +use std::hint::black_box; + +use criterion::Criterion; +use valence::packet::{Decode, Encode}; + +pub fn decode_array(c: &mut Criterion) { + let floats = [123.0, 456.0, 789.0]; + let mut buf = [0u8; 24]; + + floats.encode(buf.as_mut_slice()).unwrap(); + + c.bench_function("<[f64; 3]>::decode", |b| { + b.iter(|| { + let mut r = black_box(buf.as_slice()); + let _ = black_box(<[f64; 3]>::decode(&mut r)); + }); + }); + + let bytes = [42; 4096]; + + c.bench_function("<[u8; 4096]>::decode", |b| { + b.iter(|| { + let mut r = black_box(bytes.as_slice()); + let _ = black_box(<[u8; 4096]>::decode(&mut r)); + }) + }); +} diff --git a/crates/valence/benches/main.rs b/crates/valence/benches/main.rs new file mode 100644 index 0000000..19ea591 --- /dev/null +++ b/crates/valence/benches/main.rs @@ -0,0 +1,20 @@ +use criterion::{criterion_group, criterion_main}; + +mod anvil; +mod block; +mod decode_array; +mod packet; +mod var_int; +mod var_long; + +criterion_group! { + benches, + anvil::load, + block::block, + decode_array::decode_array, + packet::packet, + var_int::var_int, + var_long::var_long, +} + +criterion_main!(benches); diff --git a/crates/valence_protocol/benches/benches.rs b/crates/valence/benches/packet.rs similarity index 52% rename from crates/valence_protocol/benches/benches.rs rename to crates/valence/benches/packet.rs index 5b8ca8c..476f8eb 100644 --- a/crates/valence_protocol/benches/benches.rs +++ b/crates/valence/benches/packet.rs @@ -1,114 +1,18 @@ use std::borrow::Cow; -use std::time::Duration; +use std::hint::black_box; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use rand::Rng; -use valence_nbt::{compound, List}; -use valence_protocol::array::LengthPrefixedArray; -use valence_protocol::block::{BlockKind, BlockState, PropName, PropValue}; -use valence_protocol::byte_angle::ByteAngle; -use valence_protocol::decoder::{decode_packet, PacketDecoder}; -use valence_protocol::encoder::{encode_packet, encode_packet_compressed, PacketEncoder}; -use valence_protocol::item::ItemKind; -use valence_protocol::packet::s2c::play::{ChunkDataS2c, EntitySpawnS2c, PlayerListHeaderS2c}; -use valence_protocol::text::{Color, TextFormat}; -use valence_protocol::var_int::VarInt; -use valence_protocol::var_long::VarLong; -use valence_protocol::{Decode, Encode}; +use criterion::Criterion; +use valence::nbt::{compound, List}; +use valence::packet::array::LengthPrefixedArray; +use valence::packet::byte_angle::ByteAngle; +use valence::packet::decode::{decode_packet, PacketDecoder}; +use valence::packet::encode::{encode_packet, encode_packet_compressed, PacketEncoder}; +use valence::packet::s2c::play::{ChunkDataS2c, EntitySpawnS2c, PlayerListHeaderS2c}; +use valence::packet::var_int::VarInt; +use valence::prelude::*; +use valence::text::TextFormat; -criterion_group! { - name = benches; - config = Criterion::default() - .measurement_time(Duration::from_secs(5)).confidence_level(0.99); - targets = blocks, packets, var_int, var_long, decode_array -} -criterion_main!(benches); - -fn blocks(c: &mut Criterion) { - let states = BlockKind::ALL.map(BlockKind::to_state); - - c.bench_function("BlockState::from_kind", |b| { - b.iter(|| { - for kind in black_box(BlockKind::ALL) { - black_box(BlockState::from_kind(kind)); - } - }); - }); - - c.bench_function("BlockState::to_kind", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.to_kind()); - } - }); - }); - - c.bench_function("BlockState::get", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.get(PropName::Note)); - } - }); - }); - - c.bench_function("BlockState::set", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.set(PropName::Note, PropValue::Didgeridoo)); - } - }); - }); - - c.bench_function("BlockState::is_liquid", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.is_liquid()); - } - }); - }); - - c.bench_function("BlockState::is_opaque", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.is_opaque()); - } - }) - }); - - c.bench_function("BlockState::is_replaceable", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.is_replaceable()); - } - }) - }); - - c.bench_function("BlockState::luminance", |b| { - b.iter(|| { - for state in black_box(states) { - black_box(state.luminance()); - } - }) - }); - - c.bench_function("BlockKind::to_item_kind", |b| { - b.iter(|| { - for kind in black_box(BlockKind::ALL) { - black_box(kind.to_item_kind()); - } - }); - }); - - c.bench_function("BlockKind::from_item_kind", |b| { - b.iter(|| { - for kind in black_box(ItemKind::ALL) { - black_box(BlockKind::from_item_kind(kind)); - } - }); - }); -} - -fn packets(c: &mut Criterion) { +pub fn packet(c: &mut Criterion) { let mut encoder = PacketEncoder::new(); const BLOCKS_AND_BIOMES: [u8; 2000] = [0x80; 2000]; @@ -116,8 +20,7 @@ fn packets(c: &mut Criterion) { [LengthPrefixedArray([0xff; 2048]); 26]; let chunk_data_packet = ChunkDataS2c { - chunk_x: 123, - chunk_z: 456, + pos: ChunkPos::new(123, 456), heightmaps: Cow::Owned(compound! { "MOTION_BLOCKING" => List::Long(vec![123; 256]), }), @@ -146,7 +49,7 @@ fn packets(c: &mut Criterion) { entity_id: VarInt(1234), object_uuid: Default::default(), kind: VarInt(5), - position: [123.0, 456.0, 789.0], + position: DVec3::new(123.0, 456.0, 789.0), pitch: ByteAngle(200), yaw: ByteAngle(100), head_yaw: ByteAngle(50), @@ -320,86 +223,3 @@ fn packets(c: &mut Criterion) { }); }); } - -fn var_int(c: &mut Criterion) { - let mut rng = rand::thread_rng(); - - c.bench_function("VarInt::encode", |b| { - b.iter_with_setup( - || rng.gen(), - |i| { - let i: i32 = black_box(i); - - let mut buf = [0; VarInt::MAX_SIZE]; - let _ = black_box(VarInt(i).encode(buf.as_mut_slice())); - }, - ); - }); - - c.bench_function("VarInt::decode", |b| { - b.iter_with_setup( - || { - let mut buf = [0; VarInt::MAX_SIZE]; - VarInt(rng.gen()).encode(buf.as_mut_slice()).unwrap(); - buf - }, - |buf| { - let mut r = black_box(buf.as_slice()); - let _ = black_box(VarInt::decode(&mut r)); - }, - ) - }); -} - -fn var_long(c: &mut Criterion) { - let mut rng = rand::thread_rng(); - - c.bench_function("VarLong::encode", |b| { - b.iter_with_setup( - || rng.gen(), - |i| { - let i: i64 = black_box(i); - - let mut buf = [0; VarLong::MAX_SIZE]; - let _ = black_box(VarLong(i).encode(buf.as_mut_slice())); - }, - ); - }); - - c.bench_function("VarLong::decode", |b| { - b.iter_with_setup( - || { - let mut buf = [0; VarLong::MAX_SIZE]; - VarLong(rng.gen()).encode(buf.as_mut_slice()).unwrap(); - buf - }, - |buf| { - let mut r = black_box(buf.as_slice()); - let _ = black_box(VarLong::decode(&mut r)); - }, - ) - }); -} - -fn decode_array(c: &mut Criterion) { - let floats = [123.0, 456.0, 789.0]; - let mut buf = [0u8; 24]; - - floats.encode(buf.as_mut_slice()).unwrap(); - - c.bench_function("<[f64; 3]>::decode", |b| { - b.iter(|| { - let mut r = black_box(buf.as_slice()); - let _ = black_box(<[f64; 3]>::decode(&mut r)); - }); - }); - - let bytes = [42; 4096]; - - c.bench_function("<[u8; 4096]>::decode", |b| { - b.iter(|| { - let mut r = black_box(bytes.as_slice()); - let _ = black_box(<[u8; 4096]>::decode(&mut r)); - }) - }); -} diff --git a/crates/valence/benches/var_int.rs b/crates/valence/benches/var_int.rs new file mode 100644 index 0000000..f08d422 --- /dev/null +++ b/crates/valence/benches/var_int.rs @@ -0,0 +1,36 @@ +use std::hint::black_box; + +use criterion::Criterion; +use rand::Rng; +use valence::packet::var_int::VarInt; +use valence::packet::{Decode, Encode}; + +pub fn var_int(c: &mut Criterion) { + let mut rng = rand::thread_rng(); + + c.bench_function("VarInt::encode", |b| { + b.iter_with_setup( + || rng.gen(), + |i| { + let i: i32 = black_box(i); + + let mut buf = [0; VarInt::MAX_SIZE]; + let _ = black_box(VarInt(i).encode(buf.as_mut_slice())); + }, + ); + }); + + c.bench_function("VarInt::decode", |b| { + b.iter_with_setup( + || { + let mut buf = [0; VarInt::MAX_SIZE]; + VarInt(rng.gen()).encode(buf.as_mut_slice()).unwrap(); + buf + }, + |buf| { + let mut r = black_box(buf.as_slice()); + let _ = black_box(VarInt::decode(&mut r)); + }, + ) + }); +} diff --git a/crates/valence/benches/var_long.rs b/crates/valence/benches/var_long.rs new file mode 100644 index 0000000..d8fe0fb --- /dev/null +++ b/crates/valence/benches/var_long.rs @@ -0,0 +1,36 @@ +use std::hint::black_box; + +use criterion::Criterion; +use rand::Rng; +use valence::packet::var_long::VarLong; +use valence::packet::{Decode, Encode}; + +pub fn var_long(c: &mut Criterion) { + let mut rng = rand::thread_rng(); + + c.bench_function("VarLong::encode", |b| { + b.iter_with_setup( + || rng.gen(), + |i| { + let i: i64 = black_box(i); + + let mut buf = [0; VarLong::MAX_SIZE]; + let _ = black_box(VarLong(i).encode(buf.as_mut_slice())); + }, + ); + }); + + c.bench_function("VarLong::decode", |b| { + b.iter_with_setup( + || { + let mut buf = [0; VarLong::MAX_SIZE]; + VarLong(rng.gen()).encode(buf.as_mut_slice()).unwrap(); + buf + }, + |buf| { + let mut r = black_box(buf.as_slice()); + let _ = black_box(VarLong::decode(&mut r)); + }, + ) + }); +} diff --git a/crates/valence/build/entity_event.rs b/crates/valence/build/entity_event.rs deleted file mode 100644 index 9693ac8..0000000 --- a/crates/valence/build/entity_event.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::collections::BTreeMap; - -use heck::ToPascalCase; -use proc_macro2::TokenStream; -use quote::quote; -use serde::Deserialize; - -use crate::ident; - -#[derive(Deserialize, Clone, Debug)] -struct EntityEvents { - entity_status: BTreeMap, - entity_animation: BTreeMap, -} - -pub fn build() -> anyhow::Result { - let entity_events: EntityEvents = - serde_json::from_str(include_str!("../../../extracted/misc.json"))?; - - let mut statuses: Vec<_> = entity_events.entity_status.into_iter().collect(); - statuses.sort_by_key(|(_, id)| *id); - - let mut animations: Vec<_> = entity_events.entity_animation.into_iter().collect(); - animations.sort_by_key(|(_, id)| *id); - - let entity_status_variants: Vec<_> = statuses - .iter() - .map(|(name, code)| { - let name = ident(name.to_pascal_case()); - let code = *code as isize; - - quote! { - #name = #code, - } - }) - .collect(); - - let entity_animation_variants: Vec<_> = animations - .iter() - .map(|(name, code)| { - let name = ident(name.to_pascal_case()); - let code = *code as isize; - - quote! { - #name = #code, - } - }) - .collect(); - - Ok(quote! { - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] - pub enum EntityStatus { - #(#entity_status_variants)* - } - - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] - pub enum EntityAnimation { - #(#entity_animation_variants)* - } - }) -} diff --git a/crates/valence/build/main.rs b/crates/valence/build/main.rs deleted file mode 100644 index 059f4d1..0000000 --- a/crates/valence/build/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::path::Path; -use std::process::Command; -use std::{env, fs}; - -use anyhow::Context; -use proc_macro2::{Ident, Span}; - -mod entity; -mod entity_event; - -pub fn main() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed=../../extracted/"); - - let generators = [ - (entity::build as fn() -> _, "entity.rs"), - (entity_event::build, "entity_event.rs"), - ]; - - let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?; - - for (generator, file_name) in generators { - let path = Path::new(&out_dir).join(file_name); - let code = generator()?.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) -> 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()), - } -} diff --git a/crates/valence_anvil/examples/anvil_loading.rs b/crates/valence/examples/anvil_loading.rs similarity index 98% rename from crates/valence_anvil/examples/anvil_loading.rs rename to crates/valence/examples/anvil_loading.rs index 72e52ea..c1fd61b 100644 --- a/crates/valence_anvil/examples/anvil_loading.rs +++ b/crates/valence/examples/anvil_loading.rs @@ -6,8 +6,8 @@ use std::thread; use clap::Parser; use flume::{Receiver, Sender}; use tracing::warn; +use valence::anvil::{AnvilChunk, AnvilWorld}; use valence::prelude::*; -use valence_anvil::{AnvilChunk, AnvilWorld}; const SPAWN_POS: DVec3 = DVec3::new(0.0, 256.0, 0.0); const SECTION_COUNT: usize = 24; @@ -62,7 +62,7 @@ pub fn main() { }; App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .insert_resource(game_state) .add_startup_system(setup) .add_systems( diff --git a/crates/valence/examples/bench_players.rs b/crates/valence/examples/bench_players.rs index 4f42df4..f675bf4 100644 --- a/crates/valence/examples/bench_players.rs +++ b/crates/valence/examples/bench_players.rs @@ -4,6 +4,7 @@ use std::time::Instant; use valence::instance::{Chunk, Instance}; use valence::prelude::*; +use valence_network::ConnectionMode; const SPAWN_Y: i32 = 64; @@ -14,16 +15,20 @@ fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin( - ServerPlugin::new(()) - .with_connection_mode(ConnectionMode::Offline) - .with_compression_threshold(None) - .with_max_connections(50_000), - ) + .insert_resource(CoreSettings { + compression_threshold: None, + ..Default::default() + }) + .insert_resource(NetworkSettings { + connection_mode: ConnectionMode::Offline, + max_connections: 50_000, + ..Default::default() + }) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems(( record_tick_start_time.in_base_set(CoreSet::First), - print_tick_time.in_base_set(CoreSet::Last), + print_tick_time.in_base_set(CoreSet::LastFlush), init_clients, despawn_disconnected_clients, )) @@ -34,10 +39,15 @@ fn record_tick_start_time(mut commands: Commands) { commands.insert_resource(TickStart(Instant::now())); } -fn print_tick_time(server: Res, time: Res, clients: Query<(), With>) { +fn print_tick_time( + server: Res, + settings: Res, + time: Res, + clients: Query<(), With>, +) { let tick = server.current_tick(); - if tick % (server.tps() / 2) == 0 { - let client_count = clients.iter().count(); + if tick % (settings.tick_rate.get() as i64 / 2) == 0 { + let client_count = clients.iter().len(); let millis = time.0.elapsed().as_secs_f32() * 1000.0; println!("Tick={tick}, MSPT={millis:.04}ms, Clients={client_count}"); diff --git a/crates/valence/examples/biomes.rs b/crates/valence/examples/biomes.rs index 34408b4..1990c0a 100644 --- a/crates/valence/examples/biomes.rs +++ b/crates/valence/examples/biomes.rs @@ -8,7 +8,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems((init_clients, despawn_disconnected_clients)) .run(); diff --git a/crates/valence/examples/block_entities.rs b/crates/valence/examples/block_entities.rs index 8b3364b..b94a89d 100644 --- a/crates/valence/examples/block_entities.rs +++ b/crates/valence/examples/block_entities.rs @@ -3,7 +3,6 @@ use valence::client::misc::{ChatMessage, InteractBlock}; use valence::nbt::{compound, List}; use valence::prelude::*; -use valence::protocol::types::Hand; const FLOOR_Y: i32 = 64; const SIGN_POS: [i32; 3] = [3, FLOOR_Y + 1, 2]; @@ -13,7 +12,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems((event_handler, init_clients, despawn_disconnected_clients)) .run(); diff --git a/crates/valence/examples/building.rs b/crates/valence/examples/building.rs index 3276221..02d5715 100644 --- a/crates/valence/examples/building.rs +++ b/crates/valence/examples/building.rs @@ -1,9 +1,8 @@ #![allow(clippy::type_complexity)] use valence::client::misc::InteractBlock; -use valence::client::ClientInventoryState; +use valence::inventory::ClientInventoryState; use valence::prelude::*; -use valence::protocol::types::Hand; const SPAWN_Y: i32 = 64; @@ -11,7 +10,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(init_clients) .add_system(despawn_disconnected_clients) @@ -131,7 +130,7 @@ fn place_blocks( continue; }; - let Some(block_kind) = stack.item.to_block_kind() else { + let Some(block_kind) = BlockKind::from_item_kind(stack.item) else { // can't place this item as a block continue; }; diff --git a/crates/valence/examples/chest.rs b/crates/valence/examples/chest.rs index 933aa48..7e1d7bf 100644 --- a/crates/valence/examples/chest.rs +++ b/crates/valence/examples/chest.rs @@ -10,7 +10,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(init_clients) .add_systems((toggle_gamemode_on_sneak, open_chest)) diff --git a/crates/valence/examples/combat.rs b/crates/valence/examples/combat.rs index 1860184..6fd208f 100644 --- a/crates/valence/examples/combat.rs +++ b/crates/valence/examples/combat.rs @@ -20,7 +20,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(init_clients) .add_system(handle_combat_events.in_schedule(EventLoopSchedule)) diff --git a/crates/valence/examples/conway.rs b/crates/valence/examples/conway.rs index 6e69a95..a460253 100644 --- a/crates/valence/examples/conway.rs +++ b/crates/valence/examples/conway.rs @@ -23,7 +23,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup_biomes.before(setup)) .add_startup_system(setup) .add_system(init_clients) diff --git a/crates/valence/examples/cow_sphere.rs b/crates/valence/examples/cow_sphere.rs index 080a449..68263c5 100644 --- a/crates/valence/examples/cow_sphere.rs +++ b/crates/valence/examples/cow_sphere.rs @@ -23,7 +23,7 @@ fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(init_clients) .add_system(update_sphere) @@ -77,10 +77,11 @@ fn init_clients( } fn update_sphere( + settings: Res, server: Res, mut parts: Query<(&mut Position, &mut Look, &mut HeadYaw), With>, ) { - let time = server.current_tick() as f64 / server.tps() as f64; + let time = server.current_tick() as f64 / settings.tick_rate.get() as f64; let rot_angles = DVec3::new(0.2, 0.4, 0.6) * SPHERE_FREQ * time * TAU % TAU; let rot = DQuat::from_euler(EulerRot::XYZ, rot_angles.x, rot_angles.y, rot_angles.z); diff --git a/crates/valence/examples/death.rs b/crates/valence/examples/death.rs index 8aca8a4..9232480 100644 --- a/crates/valence/examples/death.rs +++ b/crates/valence/examples/death.rs @@ -9,7 +9,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(()).with_connection_mode(ConnectionMode::Offline)) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems((init_clients, squat_and_die, necromancy)) .add_system(despawn_disconnected_clients) @@ -88,7 +88,7 @@ fn necromancy( // make the client respawn in another instance let idx = instances.iter().position(|i| i == loc.0).unwrap(); - let count = instances.iter().count(); + let count = instances.iter().len(); loc.0 = instances.into_iter().nth((idx + 1) % count).unwrap(); } diff --git a/crates/valence/examples/parkour.rs b/crates/valence/examples/parkour.rs index 857c8c5..a94fb8e 100644 --- a/crates/valence/examples/parkour.rs +++ b/crates/valence/examples/parkour.rs @@ -5,10 +5,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; use rand::seq::SliceRandom; use rand::Rng; +use valence::packet::s2c::play::TitleFadeS2c; use valence::prelude::*; -use valence::protocol::packet::s2c::play::TitleFadeS2c; -use valence::protocol::sound::Sound; -use valence::protocol::types::SoundCategory; +use valence::sound::{Sound, SoundCategory}; const START_POS: BlockPos = BlockPos::new(0, 100, 0); const VIEW_DIST: u8 = 10; @@ -27,7 +26,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_system(init_clients) .add_systems(( reset_clients.after(init_clients), diff --git a/crates/valence/examples/particles.rs b/crates/valence/examples/particles.rs index 84301c3..5871e22 100644 --- a/crates/valence/examples/particles.rs +++ b/crates/valence/examples/particles.rs @@ -10,7 +10,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(init_clients) .add_system(despawn_disconnected_clients) @@ -91,8 +91,8 @@ fn create_particle_vec() -> Vec { vec![ Particle::AmbientEntityEffect, Particle::AngryVillager, - Particle::Block(BlockState::OAK_PLANKS), - Particle::BlockMarker(BlockState::GOLD_BLOCK), + Particle::Block(BlockState::OAK_PLANKS.to_raw() as _), + Particle::BlockMarker(BlockState::GOLD_BLOCK.to_raw() as _), Particle::Bubble, Particle::Cloud, Particle::Crit, @@ -104,13 +104,13 @@ fn create_particle_vec() -> Vec { Particle::DrippingWater, Particle::FallingWater, Particle::Dust { - rgb: [1.0, 1.0, 0.0], + rgb: Vec3::new(1.0, 1.0, 0.0), scale: 2.0, }, Particle::DustColorTransition { - from_rgb: [1.0, 0.0, 0.0], + from_rgb: Vec3::new(1.0, 0.0, 0.0), scale: 2.0, - to_rgb: [0.0, 1.0, 0.0], + to_rgb: Vec3::new(0.0, 1.0, 0.0), }, Particle::Effect, Particle::ElderGuardian, @@ -121,7 +121,7 @@ fn create_particle_vec() -> Vec { Particle::ExplosionEmitter, Particle::Explosion, Particle::SonicBoom, - Particle::FallingDust(BlockState::RED_SAND), + Particle::FallingDust(BlockState::RED_SAND.to_raw() as _), Particle::Firework, Particle::Fishing, Particle::Flame, diff --git a/crates/valence/examples/player_list.rs b/crates/valence/examples/player_list.rs index 95dbd51..761ec01 100644 --- a/crates/valence/examples/player_list.rs +++ b/crates/valence/examples/player_list.rs @@ -3,6 +3,7 @@ use rand::Rng; use valence::player_list::{DisplayName, PlayerListEntryBundle}; use valence::prelude::*; +use valence_client::Ping; const SPAWN_Y: i32 = 64; const PLAYER_UUID_1: Uuid = Uuid::from_u128(1); @@ -12,7 +13,7 @@ fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems(( init_clients, diff --git a/crates/valence/examples/resource_pack.rs b/crates/valence/examples/resource_pack.rs index f02f0aa..d3e0697 100644 --- a/crates/valence/examples/resource_pack.rs +++ b/crates/valence/examples/resource_pack.rs @@ -3,8 +3,8 @@ use valence::client::misc::{ResourcePackStatus, ResourcePackStatusChange}; use valence::entity::player::PlayerEntityBundle; use valence::entity::sheep::SheepEntityBundle; +use valence::packet::c2s::play::player_interact_entity::EntityInteraction; use valence::prelude::*; -use valence::protocol::packet::c2s::play::player_interact_entity::EntityInteraction; const SPAWN_Y: i32 = 64; @@ -12,7 +12,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems((init_clients, prompt_on_punch, on_resource_pack_status)) .add_system(despawn_disconnected_clients) diff --git a/crates/valence/examples/server_list_ping.rs b/crates/valence/examples/server_list_ping.rs index de697e1..7ce3793 100644 --- a/crates/valence/examples/server_list_ping.rs +++ b/crates/valence/examples/server_list_ping.rs @@ -2,27 +2,38 @@ use std::net::SocketAddr; +use rand::Rng; use valence::prelude::*; +use valence_network::{async_trait, CleanupFn, ConnectionMode, PlayerSampleEntry, ServerListPing}; pub fn main() { + tracing_subscriber::fmt().init(); + App::new() - .add_plugin(ServerPlugin::new(MyCallbacks).with_connection_mode(ConnectionMode::Offline)) + .insert_resource(NetworkSettings { + connection_mode: ConnectionMode::Offline, + callbacks: MyCallbacks.into(), + ..Default::default() + }) + .add_plugins(DefaultPlugins) .run(); } struct MyCallbacks; #[async_trait] -impl AsyncCallbacks for MyCallbacks { +impl NetworkCallbacks for MyCallbacks { async fn server_list_ping( &self, - _shared: &SharedServer, + _shared: &SharedNetworkState, remote_addr: SocketAddr, _protocol_version: i32, ) -> ServerListPing { + let max_players = 420; + ServerListPing::Respond { - online_players: 42, - max_players: 420, + online_players: rand::thread_rng().gen_range(0..=max_players), + max_players, player_sample: vec![PlayerSampleEntry { name: "foobar".into(), id: Uuid::from_u128(12345), @@ -33,7 +44,11 @@ impl AsyncCallbacks for MyCallbacks { } } - async fn login(&self, _shared: &SharedServer, _info: &NewClientInfo) -> Result<(), Text> { + async fn login( + &self, + _shared: &SharedNetworkState, + _info: &NewClientInfo, + ) -> Result { Err("You are not meant to join this example".color(Color::RED)) } } diff --git a/crates/valence/examples/terrain.rs b/crates/valence/examples/terrain.rs index fef2b53..2551b40 100644 --- a/crates/valence/examples/terrain.rs +++ b/crates/valence/examples/terrain.rs @@ -42,7 +42,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_systems( ( diff --git a/crates/valence/examples/text.rs b/crates/valence/examples/text.rs index c23e6d1..7e8847e 100644 --- a/crates/valence/examples/text.rs +++ b/crates/valence/examples/text.rs @@ -1,7 +1,6 @@ #![allow(clippy::type_complexity)] use valence::prelude::*; -use valence::protocol::translation_key; const SPAWN_Y: i32 = 64; @@ -9,7 +8,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(())) + .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(init_clients) .add_system(despawn_disconnected_clients) diff --git a/crates/valence/src/component.rs b/crates/valence/src/component.rs deleted file mode 100644 index 19d1e1b..0000000 --- a/crates/valence/src/component.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::fmt; - -use bevy_app::{CoreSet, Plugin}; -/// Contains shared components and world queries. -use bevy_ecs::prelude::*; -use glam::{DVec3, Vec3}; -use uuid::Uuid; -use valence_protocol::types::{GameMode as ProtocolGameMode, Property}; - -use crate::client::FlushPacketsSet; -use crate::util::{from_yaw_and_pitch, to_yaw_and_pitch}; -use crate::view::ChunkPos; - -pub(crate) struct ComponentPlugin; - -impl Plugin for ComponentPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.add_systems( - (update_old_position, update_old_location) - .in_base_set(CoreSet::PostUpdate) - .after(FlushPacketsSet), - ) - .add_system(despawn_marked_entities.in_base_set(CoreSet::Last)); - } -} - -fn update_old_position(mut query: Query<(&Position, &mut OldPosition)>) { - for (pos, mut old_pos) in &mut query { - old_pos.0 = pos.0; - } -} - -fn update_old_location(mut query: Query<(&Location, &mut OldLocation)>) { - for (loc, mut old_loc) in &mut query { - old_loc.0 = loc.0; - } -} - -/// Despawns all the entities marked as despawned with the [`Despawned`] -/// component. -fn despawn_marked_entities(mut commands: Commands, entities: Query>) { - for entity in &entities { - commands.entity(entity).despawn(); - } -} - -/// A [`Component`] for marking entities that should be despawned at the end of -/// the tick. -/// -/// In Valence, some entities such as [Minecraft entities] are not allowed to -/// be removed from the [`World`] directly. Instead, you must give the entities -/// you wish to despawn the `Despawned` component. At the end of the tick, -/// Valence will despawn all entities with this component for you. -/// -/// It is legal to remove components or delete entities that Valence does not -/// know about at any time. -/// -/// [Minecraft entities]: crate::entity -#[derive(Component, Copy, Clone, Default, PartialEq, Eq, Debug)] -pub struct Despawned; - -/// The universally unique identifier of an entity. Component wrapper for a -/// [`Uuid`]. -/// -/// This component is expected to remain _unique_ and _constant_ during the -/// lifetime of the entity. The [`Default`] impl generates a new random UUID. -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] -pub struct UniqueId(pub Uuid); - -/// Generates a new random UUID. -impl Default for UniqueId { - fn default() -> Self { - Self(Uuid::from_bytes(rand::random())) - } -} - -#[derive(Component, Clone, PartialEq, Eq, Default, Debug)] -pub struct Username(pub String); - -impl fmt::Display for Username { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -#[derive(Component, Clone, PartialEq, Eq, Default, Debug)] -pub struct Properties(pub Vec); - -impl Properties { - /// Finds the property with the name "textures". - pub fn textures(&self) -> Option<&Property> { - self.0.iter().find(|prop| prop.name == "textures") - } - - /// Finds the property with the name "textures". - pub fn textures_mut(&mut self) -> Option<&mut Property> { - self.0.iter_mut().find(|prop| prop.name == "textures") - } -} - -#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] - -pub enum GameMode { - #[default] - Survival, - Creative, - Adventure, - Spectator, -} - -impl From for ProtocolGameMode { - fn from(gm: GameMode) -> Self { - match gm { - GameMode::Survival => ProtocolGameMode::Survival, - GameMode::Creative => ProtocolGameMode::Creative, - GameMode::Adventure => ProtocolGameMode::Adventure, - GameMode::Spectator => ProtocolGameMode::Spectator, - } - } -} - -impl From for GameMode { - fn from(gm: ProtocolGameMode) -> Self { - match gm { - ProtocolGameMode::Survival => GameMode::Survival, - ProtocolGameMode::Creative => GameMode::Creative, - ProtocolGameMode::Adventure => GameMode::Adventure, - ProtocolGameMode::Spectator => GameMode::Spectator, - } - } -} - -/// Delay measured in milliseconds. Negative values indicate absence. -#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct Ping(pub i32); - -impl Default for Ping { - fn default() -> Self { - Self(-1) - } -} - -/// Contains the [`Instance`] an entity is located in. For the coordinates -/// within the instance, see [`Position`]. -/// -/// [`Instance`]: crate::instance::Instance -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] -pub struct Location(pub Entity); - -impl Default for Location { - fn default() -> Self { - Self(Entity::PLACEHOLDER) - } -} - -impl PartialEq for Location { - fn eq(&self, other: &OldLocation) -> bool { - self.0 == other.0 - } -} - -/// The value of [`Location`] from the end of the previous tick. -/// -/// **NOTE**: You should not modify this component after the entity is spawned. -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] -pub struct OldLocation(Entity); - -impl OldLocation { - pub fn new(instance: Entity) -> Self { - Self(instance) - } - - pub fn get(&self) -> Entity { - self.0 - } -} - -impl Default for OldLocation { - fn default() -> Self { - Self(Entity::PLACEHOLDER) - } -} - -impl PartialEq for OldLocation { - fn eq(&self, other: &Location) -> bool { - self.0 == other.0 - } -} - -#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] -pub struct Position(pub DVec3); - -impl Position { - pub fn new(pos: impl Into) -> Self { - Self(pos.into()) - } - - pub fn chunk_pos(&self) -> ChunkPos { - ChunkPos::from_dvec3(self.0) - } - - pub fn get(self) -> DVec3 { - self.0 - } - - pub fn set(&mut self, pos: impl Into) { - self.0 = pos.into(); - } -} - -impl PartialEq for Position { - fn eq(&self, other: &OldPosition) -> bool { - self.0 == other.0 - } -} - -/// The value of [`Location`] from the end of the previous tick. -/// -/// **NOTE**: You should not modify this component after the entity is spawned. -#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] -pub struct OldPosition(DVec3); - -impl OldPosition { - pub fn new(pos: impl Into) -> Self { - Self(pos.into()) - } - - pub fn get(self) -> DVec3 { - self.0 - } - - pub fn chunk_pos(self) -> ChunkPos { - ChunkPos::from_dvec3(self.0) - } -} - -impl PartialEq for OldPosition { - fn eq(&self, other: &Position) -> bool { - self.0 == other.0 - } -} - -/// Describes the direction an entity is looking using pitch and yaw angles. -#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] -pub struct Look { - /// The yaw angle in degrees. - pub yaw: f32, - /// The pitch angle in degrees. - pub pitch: f32, -} - -impl Look { - pub const fn new(yaw: f32, pitch: f32) -> Self { - Self { yaw, pitch } - } - - /// Gets a normalized direction vector from the yaw and pitch. - pub fn vec(&self) -> Vec3 { - from_yaw_and_pitch(self.yaw, self.pitch) - } - - /// Sets the yaw and pitch using a normalized direction vector. - pub fn set_vec(&mut self, vec: Vec3) { - (self.yaw, self.pitch) = to_yaw_and_pitch(vec); - } -} - -#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] -pub struct OnGround(pub bool); - -/// General-purpose reusable byte buffer. -/// -/// No guarantees are made about the buffer's contents between systems. -/// Therefore, the inner `Vec` should be cleared before use. -#[derive(Component, Default, Debug)] -pub struct ScratchBuf(pub Vec); diff --git a/crates/valence/src/config.rs b/crates/valence/src/config.rs deleted file mode 100644 index d5e1c19..0000000 --- a/crates/valence/src/config.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; -use std::sync::Arc; - -use async_trait::async_trait; -use bevy_app::{App, Plugin}; -use serde::Serialize; -use tokio::runtime::Handle; -use tracing::error; -use uuid::Uuid; -use valence_protocol::text::Text; - -use crate::server::{NewClientInfo, SharedServer}; - -#[derive(Clone)] -#[non_exhaustive] -pub struct ServerPlugin { - pub callbacks: Arc, - /// The [`Handle`] to the tokio runtime the server will use. If `None` is - /// provided, the server will create its own tokio runtime at startup. - /// - /// # Default Value - /// - /// `None` - pub tokio_handle: Option, - /// The maximum number of simultaneous connections allowed to the server. - /// This includes all connections, not just those past the login stage. - /// - /// You will want this value to be somewhere above the maximum number of - /// players, since status pings should still succeed even when the server is - /// full. - /// - /// # Default Value - /// - /// `1024`. This may change in a future version. - pub max_connections: usize, - /// The socket address the server will be bound to. - /// - /// # Default Value - /// - /// `0.0.0.0:25565`, which will listen on every available network interface. - pub address: SocketAddr, - /// The ticks per second of the server. This is the number of game updates - /// that should occur in one second. - /// - /// On each game update (tick), the server is expected to update game logic - /// and respond to packets from clients. Once this is complete, the server - /// will sleep for any remaining time until a full tick has passed. - /// - /// The tick rate must be greater than zero. - /// - /// Note that the official Minecraft client only processes packets at 20hz, - /// so there is little benefit to a tick rate higher than 20. - /// - /// # Default Value - /// - /// [`DEFAULT_TPS`] - pub tps: i64, - /// The connection mode. This determines if client authentication and - /// encryption should take place and if the server should get the player - /// data from a proxy. - /// - /// # Default Value - /// - /// [`ConnectionMode::Online`] - pub connection_mode: ConnectionMode, - /// The compression threshold to use for compressing packets. For a - /// compression threshold of `Some(N)`, packets with encoded lengths >= `N` - /// are compressed while all others are not. `None` disables compression - /// completely. - /// - /// If the server is used behind a proxy on the same machine, you will - /// likely want to disable compression. - /// - /// # Default Value - /// - /// Compression is enabled with an unspecified threshold. - pub compression_threshold: Option, - /// The maximum capacity (in bytes) of the buffer used to hold incoming - /// packet data. - /// - /// A larger capacity reduces the chance that a client needs to be - /// disconnected due to the buffer being full, but increases potential - /// memory usage. - /// - /// # Default Value - /// - /// An unspecified value is used that should be adequate for most - /// situations. This default may change in future versions. - pub incoming_capacity: usize, - /// The maximum capacity (in bytes) of the buffer used to hold outgoing - /// packets. - /// - /// A larger capacity reduces the chance that a client needs to be - /// disconnected due to the buffer being full, but increases potential - /// memory usage. - /// - /// # Default Value - /// - /// An unspecified value is used that should be adequate for most - /// situations. This default may change in future versions. - pub outgoing_capacity: usize, -} - -impl ServerPlugin { - pub fn new(callbacks: impl Into>) -> Self { - Self { - callbacks: callbacks.into(), - tokio_handle: None, - max_connections: 1024, - address: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 25565).into(), - tps: DEFAULT_TPS, - connection_mode: ConnectionMode::Online { - // Note: Some people have problems using valence when this is enabled by default. - prevent_proxy_connections: false, - }, - compression_threshold: Some(256), - incoming_capacity: 2097152, // 2 MiB - outgoing_capacity: 8388608, // 8 MiB - } - } - - /// See [`Self::tokio_handle`]. - #[must_use] - pub fn with_tokio_handle(mut self, tokio_handle: Option) -> Self { - self.tokio_handle = tokio_handle; - self - } - - /// See [`Self::max_connections`]. - #[must_use] - pub fn with_max_connections(mut self, max_connections: usize) -> Self { - self.max_connections = max_connections; - self - } - - /// See [`Self::address`]. - #[must_use] - pub fn with_address(mut self, address: SocketAddr) -> Self { - self.address = address; - self - } - - /// See [`Self::tps`]. - #[must_use] - pub fn with_tick_rate(mut self, tick_rate: i64) -> Self { - self.tps = tick_rate; - self - } - - /// See [`Self::connection_mode`]. - #[must_use] - pub fn with_connection_mode(mut self, connection_mode: ConnectionMode) -> Self { - self.connection_mode = connection_mode; - self - } - - /// See [`Self::compression_threshold`]. - #[must_use] - pub fn with_compression_threshold(mut self, compression_threshold: Option) -> Self { - self.compression_threshold = compression_threshold; - self - } - - /// See [`Self::incoming_capacity`]. - #[must_use] - pub fn with_incoming_capacity(mut self, incoming_capacity: usize) -> Self { - self.incoming_capacity = incoming_capacity; - self - } - - /// See [`Self::outgoing_capacity`]. - #[must_use] - pub fn with_outgoing_capacity(mut self, outgoing_capacity: usize) -> Self { - self.outgoing_capacity = outgoing_capacity; - self - } -} - -impl Default for ServerPlugin { - fn default() -> Self { - Self::new(A::default()) - } -} - -impl Plugin for ServerPlugin { - fn build(&self, app: &mut App) { - if let Err(e) = crate::server::build_plugin(self, app) { - error!("failed to build Valence plugin: {e:#}"); - } - } -} - -#[async_trait] -pub trait AsyncCallbacks: Send + Sync + 'static { - /// Called when the server receives a Server List Ping query. - /// Data for the response can be provided or the query can be ignored. - /// - /// This function is called from within a tokio runtime. - /// - /// # Default Implementation - /// - /// A default placeholder response is returned. - async fn server_list_ping( - &self, - shared: &SharedServer, - remote_addr: SocketAddr, - protocol_version: i32, - ) -> ServerListPing { - #![allow(unused_variables)] - ServerListPing::Respond { - online_players: 0, // TODO: get online players. - max_players: -1, - player_sample: vec![], - description: "A Valence Server".into(), - favicon_png: &[], - } - } - - /// Called for each client after successful authentication (if online mode - /// is enabled) to determine if they can join the server. On success, a - /// new entity is spawned with the [`Client`] component. If this method - /// returns with `Err(reason)`, then the client is immediately - /// disconnected with `reason` as the displayed message. - /// - /// This method is the appropriate place to perform asynchronous - /// operations such as database queries which may take some time to - /// complete. - /// - /// This method is called from within a tokio runtime. - /// - /// # Default Implementation - /// - /// The client is allowed to join unconditionally. - /// - /// [`Client`]: crate::client::Client - async fn login(&self, shared: &SharedServer, info: &NewClientInfo) -> Result<(), Text> { - #![allow(unused_variables)] - Ok(()) - } - - /// Called upon every client login to obtain the full URL to use for session - /// server requests. This is done to authenticate player accounts. This - /// method is not called unless [online mode] is enabled. - /// - /// It is assumed that upon successful request, a structure matching the - /// description in the [wiki](https://wiki.vg/Protocol_Encryption#Server) was obtained. - /// Providing a URL that does not return such a structure will result in a - /// disconnect for every client that connects. - /// - /// The arguments are described in the linked wiki article. - /// - /// # Default Implementation - /// - /// Uses the official Minecraft session server. This is formatted as - /// `https://sessionserver.mojang.com/session/minecraft/hasJoined?username=&serverId=&ip=`. - /// - /// [online mode]: crate::config::ConnectionMode::Online - async fn session_server( - &self, - shared: &SharedServer, - username: &str, - auth_digest: &str, - player_ip: &IpAddr, - ) -> String { - if shared.connection_mode() - == (&ConnectionMode::Online { - prevent_proxy_connections: true, - }) - { - format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={auth_digest}&ip={player_ip}") - } else { - format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={auth_digest}") - } - } -} - -/// The default async callbacks. -impl AsyncCallbacks for () {} - -/// Describes how new connections to the server are handled. -#[derive(Clone, PartialEq)] -#[non_exhaustive] -pub enum ConnectionMode { - /// The "online mode" fetches all player data (username, UUID, and skin) - /// from the [configured session server] and enables encryption. - /// - /// This mode should be used by all publicly exposed servers which are not - /// behind a proxy. - /// - /// [configured session server]: AsyncCallbacks::session_server - Online { - /// Determines if client IP validation should take place during - /// authentication. - /// - /// When `prevent_proxy_connections` is enabled, clients can no longer - /// log-in if they connected to the Yggdrasil server using a different - /// IP than the one used to connect to this server. - /// - /// This is used by the default implementation of - /// [`AsyncCallbacks::session_server`]. A different implementation may - /// choose to ignore this value. - prevent_proxy_connections: bool, - }, - /// Disables client authentication with the configured session server. - /// Clients can join with any username and UUID they choose, potentially - /// gaining privileges they would not otherwise have. Additionally, - /// encryption is disabled and Minecraft's default skins will be used. - /// - /// This mode should be used for development purposes only and not for - /// publicly exposed servers. - Offline, - /// This mode should be used under one of the following situations: - /// - The server is behind a [BungeeCord]/[Waterfall] proxy with IP - /// forwarding enabled. - /// - The server is behind a [Velocity] proxy configured to use the `legacy` - /// forwarding mode. - /// - /// All player data (username, UUID, and skin) is fetched from the proxy, - /// but no attempt is made to stop connections originating from - /// elsewhere. As a result, you must ensure clients connect through the - /// proxy and are unable to connect to the server directly. Otherwise, - /// clients can use any username or UUID they choose similar to - /// [`ConnectionMode::Offline`]. - /// - /// To protect against this, a firewall can be used. However, - /// [`ConnectionMode::Velocity`] is recommended as a secure alternative. - /// - /// [BungeeCord]: https://www.spigotmc.org/wiki/bungeecord/ - /// [Waterfall]: https://github.com/PaperMC/Waterfall - /// [Velocity]: https://velocitypowered.com/ - BungeeCord, - /// This mode is used when the server is behind a [Velocity] proxy - /// configured with the forwarding mode `modern`. - /// - /// All player data (username, UUID, and skin) is fetched from the proxy and - /// all connections originating from outside Velocity are blocked. - /// - /// [Velocity]: https://velocitypowered.com/ - Velocity { - /// The secret key used to prevent connections from outside Velocity. - /// The proxy and Valence must be configured to use the same secret key. - secret: Arc, - }, -} - -/// Minecraft's standard ticks per second (TPS). -pub const DEFAULT_TPS: i64 = 20; - -/// The result of the Server List Ping [callback]. -/// -/// [callback]: crate::config::AsyncCallbacks -#[derive(Clone, Default, Debug)] -pub enum ServerListPing<'a> { - /// Responds to the server list ping with the given information. - Respond { - /// Displayed as the number of players on the server. - online_players: i32, - /// Displayed as the maximum number of players allowed on the server at - /// a time. - max_players: i32, - /// The list of players visible by hovering over the player count. - /// - /// Has no effect if this list is empty. - player_sample: Vec, - /// A description of the server. - description: Text, - /// The server's icon as the bytes of a PNG image. - /// The image must be 64x64 pixels. - /// - /// No icon is used if the slice is empty. - favicon_png: &'a [u8], - }, - /// Ignores the query and disconnects from the client. - #[default] - Ignore, -} - -/// Represents an individual entry in the player sample. -#[derive(Clone, Debug, Serialize)] -pub struct PlayerSampleEntry { - /// The name of the player. - /// - /// This string can contain - /// [legacy formatting codes](https://minecraft.fandom.com/wiki/Formatting_codes). - pub name: String, - /// The player UUID. - pub id: Uuid, -} diff --git a/crates/valence/src/lib.rs b/crates/valence/src/lib.rs index 8252b14..4562b88 100644 --- a/crates/valence/src/lib.rs +++ b/crates/valence/src/lib.rs @@ -1,5 +1,4 @@ -//! Valence is a Minecraft server framework written in Rust. - +#![doc = include_str!("../../../README.md")] // Points to the main project README. #![doc( html_logo_url = "https://raw.githubusercontent.com/valence-rs/valence/main/assets/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/valence-rs/valence/main/assets/logo.svg" @@ -18,72 +17,133 @@ trivial_numeric_casts, unused_lifetimes, unused_import_braces, + unreachable_pub, clippy::dbg_macro )] -#![allow(clippy::type_complexity)] // ECS queries are often complicated. +use bevy_app::{PluginGroup, PluginGroupBuilder}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "anvil")] +pub use valence_anvil as anvil; +pub use valence_core::*; +#[cfg(feature = "inventory")] +pub use valence_inventory as inventory; +#[cfg(feature = "network")] +pub use valence_network as network; +#[cfg(feature = "player_list")] +pub use valence_player_list as player_list; pub use { - anyhow, async_trait, bevy_app, bevy_ecs, uuid, valence_nbt as nbt, valence_protocol as protocol, + bevy_app as app, bevy_ecs as ecs, glam, valence_biome as biome, valence_block as block, + valence_client as client, valence_dimension as dimension, valence_entity as entity, + valence_instance as instance, valence_nbt as nbt, valence_registry as registry, }; -pub mod biome; -pub mod client; -pub mod component; -pub mod config; -pub mod dimension; -pub mod entity; -pub mod event_loop; -pub mod instance; -pub mod inventory; -pub mod packet; -pub mod player_list; -pub mod player_textures; -pub mod registry_codec; -pub mod server; -#[cfg(any(test, doctest))] -mod unit_test; -pub mod util; -pub mod view; -pub mod weather; - +/// Contains the most frequently used items in Valence projects. +/// +/// This is usually glob imported like so: +/// +/// ``` +/// use valence::prelude::*; // Glob import. +/// +/// let mut app = App::new(); +/// app.add_system(|| println!("yippee!")); +/// // ... +/// ``` pub mod prelude { - pub use async_trait::async_trait; - pub use bevy_app::prelude::*; - pub use bevy_ecs::prelude::*; + pub use ::uuid::Uuid; + pub use app::prelude::*; pub use biome::{Biome, BiomeId, BiomeRegistry}; + pub use block::{BlockKind, BlockState, PropName, PropValue}; + pub use block_pos::BlockPos; + pub use chunk_pos::{ChunkPos, ChunkView}; pub use client::action::*; pub use client::command::*; + pub use client::event_loop::{EventLoopSchedule, EventLoopSet}; pub use client::interact_entity::*; pub use client::{ - despawn_disconnected_clients, Client, CompassPos, CursorItem, DeathLocation, - HasRespawnScreen, HashedSeed, Ip, IsDebug, IsFlat, IsHardcore, OldView, OldViewDistance, - OpLevel, PrevGameMode, ReducedDebugInfo, View, ViewDistance, - }; - pub use component::*; - pub use config::{ - AsyncCallbacks, ConnectionMode, PlayerSampleEntry, ServerListPing, ServerPlugin, + despawn_disconnected_clients, Client, CompassPos, DeathLocation, HasRespawnScreen, + HashedSeed, Ip, IsDebug, IsFlat, IsHardcore, OldView, OldViewDistance, OpLevel, + PrevGameMode, Properties, ReducedDebugInfo, Username, View, ViewDistance, }; + pub use despawn::Despawned; pub use dimension::{DimensionType, DimensionTypeRegistry}; - pub use entity::{EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw}; - pub use event_loop::{EventLoopSchedule, EventLoopSet}; - pub use glam::DVec3; - pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance}; - pub use inventory::{ - Inventory, InventoryKind, InventoryWindow, InventoryWindowMut, OpenInventory, + pub use direction::Direction; + pub use ecs::prelude::*; + pub use entity::{ + EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw, Location, Look, + OldLocation, OldPosition, Position, }; + pub use game_mode::GameMode; + pub use glam::{DVec2, DVec3, Vec2, Vec3}; + pub use hand::Hand; + pub use ident::Ident; + pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance}; + #[cfg(feature = "inventory")] + pub use inventory::{ + CursorItem, Inventory, InventoryKind, InventoryWindow, InventoryWindowMut, OpenInventory, + }; + pub use item::{ItemKind, ItemStack}; + pub use nbt::Compound; + #[cfg(feature = "network")] + pub use network::{ + ErasedNetworkCallbacks, NetworkCallbacks, NetworkSettings, NewClientInfo, + SharedNetworkState, + }; + pub use packet::s2c::play::particle::Particle; + #[cfg(feature = "player_list")] pub use player_list::{PlayerList, PlayerListEntry}; - pub use protocol::block::{BlockState, PropName, PropValue}; - pub use protocol::ident::Ident; - pub use protocol::item::{ItemKind, ItemStack}; - pub use protocol::text::{Color, Text, TextFormat}; - pub use server::{NewClientInfo, Server, SharedServer}; - pub use uuid::Uuid; - pub use valence_nbt::Compound; - pub use valence_protocol::block::BlockKind; - pub use valence_protocol::block_pos::BlockPos; - pub use valence_protocol::ident; - pub use valence_protocol::packet::s2c::play::particle::Particle; - pub use view::{ChunkPos, ChunkView}; + pub use text::{Color, Text, TextFormat}; + pub use valence_core::ident; // Export the `ident!` macro. + pub use valence_core::uuid::UniqueId; + pub use valence_core::{translation_key, CoreSettings, Server}; + pub use super::DefaultPlugins; use super::*; } + +/// This plugin group will add all the default plugins for a Valence +/// application. +/// +/// [`DefaultPlugins`] obeys Cargo feature flags. Users may exert control over +/// this plugin group by disabling `default-features` in their `Cargo.toml` and +/// enabling only those features that they wish to use. +pub struct DefaultPlugins; + +impl PluginGroup for DefaultPlugins { + fn build(self) -> PluginGroupBuilder { + #[allow(unused_mut)] + let mut group = PluginGroupBuilder::start::() + .add(valence_core::CorePlugin) + .add(valence_registry::RegistryPlugin) + .add(valence_biome::BiomePlugin) + .add(valence_dimension::DimensionPlugin) + .add(valence_entity::EntityPlugin) + .add(valence_instance::InstancePlugin) + .add(valence_client::ClientPlugin); + + #[cfg(feature = "network")] + { + group = group.add(valence_network::NetworkPlugin); + } + + #[cfg(feature = "player_list")] + { + group = group.add(valence_player_list::PlayerListPlugin); + } + + #[cfg(feature = "inventory")] + { + group = group.add(valence_inventory::InventoryPlugin); + } + + #[cfg(feature = "anvil")] + { + // No plugin... yet. + } + + group + } +} diff --git a/crates/valence/src/packet.rs b/crates/valence/src/packet.rs deleted file mode 100644 index 6fe2d9d..0000000 --- a/crates/valence/src/packet.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::io::Write; - -use tracing::warn; -use valence_protocol::encoder::{encode_packet, encode_packet_compressed, PacketEncoder}; -use valence_protocol::Packet; - -/// Types that can have packets written to them. -pub trait WritePacket { - /// Writes a packet to this object. Encoding errors are typically logged and - /// discarded. - fn write_packet<'a>(&mut self, packet: &impl Packet<'a>); - /// Copies raw packet data directly into this object. Don't use this unless - /// you know what you're doing. - fn write_packet_bytes(&mut self, bytes: &[u8]); -} - -impl WritePacket for &mut W { - fn write_packet<'a>(&mut self, packet: &impl Packet<'a>) { - (*self).write_packet(packet) - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - (*self).write_packet_bytes(bytes) - } -} - -/// An implementor of [`WritePacket`] backed by a `Vec` reference. -pub(crate) struct PacketWriter<'a> { - buf: &'a mut Vec, - threshold: Option, - scratch: &'a mut Vec, -} - -impl<'a> PacketWriter<'a> { - pub fn new(buf: &'a mut Vec, threshold: Option, scratch: &'a mut Vec) -> Self { - Self { - buf, - threshold, - scratch, - } - } -} - -impl WritePacket for PacketWriter<'_> { - fn write_packet<'a>(&mut self, pkt: &impl Packet<'a>) { - let res = if let Some(threshold) = self.threshold { - encode_packet_compressed(self.buf, pkt, threshold, self.scratch) - } else { - encode_packet(self.buf, pkt) - }; - - if let Err(e) = res { - warn!("failed to write packet: {e:#}"); - } - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - if let Err(e) = self.buf.write_all(bytes) { - warn!("failed to write packet bytes: {e:#}"); - } - } -} - -impl WritePacket for PacketEncoder { - fn write_packet<'a>(&mut self, packet: &impl Packet<'a>) { - if let Err(e) = self.append_packet(packet) { - warn!("failed to write packet: {e:#}"); - } - } - - fn write_packet_bytes(&mut self, bytes: &[u8]) { - self.append_bytes(bytes) - } -} diff --git a/crates/valence/src/server.rs b/crates/valence/src/server.rs deleted file mode 100644 index 668756c..0000000 --- a/crates/valence/src/server.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::net::{IpAddr, SocketAddr}; -use std::ops::Deref; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::ensure; -use bevy_app::prelude::*; -use bevy_app::{ScheduleRunnerPlugin, ScheduleRunnerSettings}; -use bevy_ecs::prelude::*; -use flume::{Receiver, Sender}; -use rand::rngs::OsRng; -use rsa::{PublicKeyParts, RsaPrivateKey}; -use tokio::runtime::{Handle, Runtime}; -use tokio::sync::Semaphore; -use uuid::Uuid; -use valence_protocol::types::Property; - -use crate::biome::BiomePlugin; -use crate::client::{ClientBundle, ClientPlugin}; -use crate::component::ComponentPlugin; -use crate::config::{AsyncCallbacks, ConnectionMode, ServerPlugin}; -use crate::dimension::DimensionPlugin; -use crate::entity::EntityPlugin; -use crate::event_loop::{EventLoopPlugin, RunEventLoopSet}; -use crate::instance::InstancePlugin; -use crate::inventory::InventoryPlugin; -use crate::player_list::PlayerListPlugin; -use crate::registry_codec::RegistryCodecPlugin; -use crate::server::connect::do_accept_loop; -use crate::weather::WeatherPlugin; - -mod byte_channel; -mod connect; -pub(crate) mod connection; - -use connection::NewClientArgs; - -/// Contains global server state accessible as a [`Resource`]. -#[derive(Resource)] -pub struct Server { - /// Incremented on every tick. - current_tick: i64, - shared: SharedServer, -} - -impl Deref for Server { - type Target = SharedServer; - - fn deref(&self) -> &Self::Target { - &self.shared - } -} - -impl Server { - /// Provides a reference to the [`SharedServer`]. - pub fn shared(&self) -> &SharedServer { - &self.shared - } - - /// Returns the number of ticks that have elapsed since the server began. - pub fn current_tick(&self) -> i64 { - self.current_tick - } -} - -/// The subset of global server state which can be shared between threads. -/// -/// `SharedServer`s are internally refcounted and are inexpensive to clone. -#[derive(Clone)] -pub struct SharedServer(Arc); - -struct SharedServerInner { - address: SocketAddr, - tps: i64, - connection_mode: ConnectionMode, - compression_threshold: Option, - max_connections: usize, - incoming_capacity: usize, - outgoing_capacity: usize, - /// The tokio handle used by the server. - tokio_handle: Handle, - /// Holding a runtime handle is not enough to keep tokio working. We need - /// to store the runtime here so we don't drop it. - _tokio_runtime: Option, - /// Sender for new clients past the login stage. - new_clients_send: Sender, - /// Receiver for new clients past the login stage. - new_clients_recv: Receiver, - /// A semaphore used to limit the number of simultaneous connections to the - /// server. Closing this semaphore stops new connections. - connection_sema: Arc, - /// The RSA keypair used for encryption with clients. - rsa_key: RsaPrivateKey, - /// The public part of `rsa_key` encoded in DER, which is an ASN.1 format. - /// This is sent to clients during the authentication process. - public_key_der: Box<[u8]>, - /// For session server requests. - http_client: reqwest::Client, -} - -impl SharedServer { - /// Gets the socket address this server is bound to. - pub fn address(&self) -> SocketAddr { - self.0.address - } - - /// Gets the configured ticks per second of this server. - pub fn tps(&self) -> i64 { - self.0.tps - } - - /// Gets the connection mode of the server. - pub fn connection_mode(&self) -> &ConnectionMode { - &self.0.connection_mode - } - - /// Gets the compression threshold for packets. `None` indicates no - /// compression. - pub fn compression_threshold(&self) -> Option { - self.0.compression_threshold - } - - /// Gets the maximum number of connections allowed to the server at once. - pub fn max_connections(&self) -> usize { - self.0.max_connections - } - - /// Gets the configured incoming capacity. - pub fn incoming_capacity(&self) -> usize { - self.0.incoming_capacity - } - - /// Gets the configured outgoing incoming capacity. - pub fn outgoing_capacity(&self) -> usize { - self.0.outgoing_capacity - } - - /// Gets a handle to the tokio instance this server is using. - pub fn tokio_handle(&self) -> &Handle { - &self.0.tokio_handle - } -} - -/// Contains information about a new client joining the server. -#[non_exhaustive] -pub struct NewClientInfo { - /// The username of the new client. - pub username: String, - /// The UUID of the new client. - pub uuid: Uuid, - /// The remote address of the new client. - pub ip: IpAddr, - /// The client's properties from the game profile. Typically contains a - /// `textures` property with the skin and cape of the player. - pub properties: Vec, -} - -pub fn build_plugin( - plugin: &ServerPlugin, - app: &mut App, -) -> anyhow::Result<()> { - ensure!( - plugin.tps > 0, - "configured tick rate must be greater than zero" - ); - ensure!( - plugin.incoming_capacity > 0, - "configured incoming packet capacity must be nonzero" - ); - ensure!( - plugin.outgoing_capacity > 0, - "configured outgoing packet capacity must be nonzero" - ); - - let rsa_key = RsaPrivateKey::new(&mut OsRng, 1024)?; - - let public_key_der = - rsa_der::public_key_to_der(&rsa_key.n().to_bytes_be(), &rsa_key.e().to_bytes_be()) - .into_boxed_slice(); - - let runtime = if plugin.tokio_handle.is_none() { - Some(Runtime::new()?) - } else { - None - }; - - let tokio_handle = match &runtime { - Some(rt) => rt.handle().clone(), - None => plugin.tokio_handle.clone().unwrap(), - }; - - let (new_clients_send, new_clients_recv) = flume::bounded(64); - - let shared = SharedServer(Arc::new(SharedServerInner { - address: plugin.address, - tps: plugin.tps, - connection_mode: plugin.connection_mode.clone(), - compression_threshold: plugin.compression_threshold, - max_connections: plugin.max_connections, - incoming_capacity: plugin.incoming_capacity, - outgoing_capacity: plugin.outgoing_capacity, - tokio_handle, - _tokio_runtime: runtime, - new_clients_send, - new_clients_recv, - connection_sema: Arc::new(Semaphore::new(plugin.max_connections)), - rsa_key, - public_key_der, - http_client: Default::default(), - })); - - let server = Server { - current_tick: 0, - shared, - }; - - let shared = server.shared.clone(); - let callbacks = plugin.callbacks.clone(); - - let start_accept_loop = move || { - let _guard = shared.tokio_handle().enter(); - - // Start accepting new connections. - tokio::spawn(do_accept_loop(shared.clone(), callbacks.clone())); - }; - - let shared = server.shared.clone(); - - // System to spawn new clients. - let spawn_new_clients = move |world: &mut World| { - for _ in 0..shared.0.new_clients_recv.len() { - let Ok(args) = shared.0.new_clients_recv.try_recv() else { - break - }; - - world.spawn(ClientBundle::new(args.info, args.conn, args.enc)); - } - }; - - let shared = server.shared.clone(); - - // Insert resources. - app.insert_resource(server); - - // Make the app loop forever at the configured TPS. - { - let tick_period = Duration::from_secs_f64((shared.tps() as f64).recip()); - - app.insert_resource(ScheduleRunnerSettings::run_loop(tick_period)) - .add_plugin(ScheduleRunnerPlugin); - } - - // Start accepting connections in `PostStartup` to allow user startup code to - // run first. - app.add_system( - start_accept_loop - .in_schedule(CoreSchedule::Startup) - .in_base_set(StartupSet::PostStartup), - ); - - // Spawn new clients before the event loop starts. - app.add_system( - spawn_new_clients - .in_base_set(CoreSet::PreUpdate) - .before(RunEventLoopSet), - ); - - app.add_system(increment_tick_counter.in_base_set(CoreSet::Last)); - - // Add internal plugins. - app.add_plugin(EventLoopPlugin) - .add_plugin(RegistryCodecPlugin) - .add_plugin(BiomePlugin) - .add_plugin(DimensionPlugin) - .add_plugin(ComponentPlugin) - .add_plugin(ClientPlugin) - .add_plugin(EntityPlugin) - .add_plugin(InstancePlugin) - .add_plugin(InventoryPlugin) - .add_plugin(PlayerListPlugin) - .add_plugin(WeatherPlugin); - - Ok(()) -} - -fn increment_tick_counter(mut server: ResMut) { - server.current_tick += 1; -} diff --git a/crates/valence/src/unit_test/util.rs b/crates/valence/src/tests.rs similarity index 73% rename from crates/valence/src/unit_test/util.rs rename to crates/valence/src/tests.rs index 65eaf56..154bbd0 100644 --- a/crates/valence/src/unit_test/util.rs +++ b/crates/valence/src/tests.rs @@ -6,45 +6,85 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings}; use bytes::{Buf, BufMut, BytesMut}; -use valence_protocol::decoder::{decode_packet, PacketDecoder}; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::packet::S2cPlayPacket; -use valence_protocol::var_int::VarInt; -use valence_protocol::{ident, Packet}; +use uuid::Uuid; +use valence_client::ClientBundleArgs; +use valence_core::packet::decode::{decode_packet, PacketDecoder}; +use valence_core::packet::encode::PacketEncoder; +use valence_core::packet::s2c::play::S2cPlayPacket; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::Packet; +use valence_core::{ident, CoreSettings, Server}; +use valence_entity::Location; +use valence_network::{ConnectionMode, NetworkSettings}; use crate::client::{ClientBundle, ClientConnection, ReceivedPacket}; -use crate::component::Location; -use crate::config::{ConnectionMode, ServerPlugin}; use crate::instance::Instance; -use crate::server::{NewClientInfo, Server}; +use crate::DefaultPlugins; + +/// Sets up valence with a single mock client. Returns the Entity of the client +/// and the corresponding MockClientHelper. +/// +/// Reduces boilerplate in unit tests. +fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) { + app.insert_resource(CoreSettings { + compression_threshold: None, + ..Default::default() + }); + + app.insert_resource(NetworkSettings { + connection_mode: ConnectionMode::Offline, + ..Default::default() + }); + + app.add_plugins(DefaultPlugins); + + let server = app.world.resource::(); + let instance = Instance::new_unit_testing(ident!("overworld"), server); + let instance_ent = app.world.spawn(instance).id(); + let (client, client_helper) = create_mock_client(); + + let client_ent = app.world.spawn(client).id(); + + // Set initial location. + app.world.get_mut::(client_ent).unwrap().0 = instance_ent; + + // Print warnings if there are ambiguities in the schedule. + app.edit_schedule(CoreSchedule::Main, |schedule| { + schedule.set_build_settings(ScheduleBuildSettings { + ambiguity_detection: LogLevel::Warn, + ..Default::default() + }); + }); + + (client_ent, client_helper) +} /// Creates a mock client bundle that can be used for unit testing. /// /// Returns the client, and a helper to inject packets as if the client sent /// them and receive packets as if the client received them. -pub(crate) fn create_mock_client(client_info: NewClientInfo) -> (ClientBundle, MockClientHelper) { - let mock_connection = MockClientConnection::new(); - let enc = PacketEncoder::new(); - let bundle = ClientBundle::new(client_info, Box::new(mock_connection.clone()), enc); +fn create_mock_client() -> (ClientBundle, MockClientHelper) { + let conn = MockClientConnection::new(); - (bundle, MockClientHelper::new(mock_connection)) -} - -/// Creates a `NewClientInfo` with the given username and a random UUID. -pub fn gen_client_info(username: impl Into) -> NewClientInfo { - NewClientInfo { - username: username.into(), - uuid: uuid::Uuid::new_v4(), - ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), + let bundle = ClientBundle::new(ClientBundleArgs { + username: "test".into(), + uuid: Uuid::from_bytes(rand::random()), + ip: "127.0.0.1".parse().unwrap(), properties: vec![], - } + conn: Box::new(conn.clone()), + enc: PacketEncoder::new(), + }); + + let helper = MockClientHelper::new(conn); + + (bundle, helper) } /// A mock client connection that can be used for testing. /// /// Safe to clone, but note that the clone will share the same buffers. #[derive(Clone)] -pub(crate) struct MockClientConnection { +struct MockClientConnection { inner: Arc>, } @@ -57,7 +97,7 @@ struct MockClientConnectionInner { } impl MockClientConnection { - pub fn new() -> Self { + fn new() -> Self { Self { inner: Arc::new(Mutex::new(MockClientConnectionInner { recv_buf: VecDeque::new(), @@ -67,7 +107,7 @@ impl MockClientConnection { } /// Injects a (Packet ID + data) frame to be received by the server. - pub fn inject_recv(&mut self, mut bytes: BytesMut) { + fn inject_recv(&mut self, mut bytes: BytesMut) { let id = VarInt::decode_partial((&mut bytes).reader()).expect("failed to decode packet ID"); self.inner @@ -81,11 +121,11 @@ impl MockClientConnection { }); } - pub fn take_sent(&mut self) -> BytesMut { + fn take_sent(&mut self) -> BytesMut { self.inner.lock().unwrap().send_buf.split() } - pub fn clear_sent(&mut self) { + fn clear_sent(&mut self) { self.inner.lock().unwrap().send_buf.clear(); } } @@ -107,7 +147,7 @@ impl ClientConnection for MockClientConnection { /// Contains the mocked client connection and helper methods to inject packets /// and read packets from the send stream. -pub struct MockClientHelper { +struct MockClientHelper { conn: MockClientConnection, dec: PacketDecoder, scratch: BytesMut, @@ -126,7 +166,7 @@ impl MockClientHelper { /// Inject a packet to be treated as a packet inbound to the server. Panics /// if the packet cannot be sent. - pub fn send<'a>(&mut self, packet: &impl Packet<'a>) { + fn send<'a>(&mut self, packet: &impl Packet<'a>) { packet .encode_packet((&mut self.scratch).writer()) .expect("failed to encode packet"); @@ -135,7 +175,7 @@ impl MockClientHelper { } /// Collect all packets that have been sent to the client. - pub fn collect_sent(&mut self) -> Vec { + fn collect_sent(&mut self) -> Vec { self.dec.queue_bytes(self.conn.take_sent()); self.collected_frames.clear(); @@ -154,47 +194,14 @@ impl MockClientHelper { .collect() } - pub fn clear_sent(&mut self) { + fn clear_sent(&mut self) { self.conn.clear_sent(); } } -/// Sets up valence with a single mock client. Returns the Entity of the client -/// and the corresponding MockClientHelper. -/// -/// Reduces boilerplate in unit tests. -pub fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) { - app.add_plugin( - ServerPlugin::new(()) - .with_compression_threshold(None) - .with_connection_mode(ConnectionMode::Offline), - ); - - let server = app.world.resource::(); - let instance = Instance::new_unit_testing(ident!("overworld"), server); - let instance_ent = app.world.spawn(instance).id(); - let (client, client_helper) = create_mock_client(gen_client_info("test")); - - let client_ent = app.world.spawn(client).id(); - - // Set initial location. - app.world.get_mut::(client_ent).unwrap().0 = instance_ent; - - // Print warnings if there are ambiguities in the schedule. - app.edit_schedule(CoreSchedule::Main, |schedule| { - schedule.set_build_settings(ScheduleBuildSettings { - ambiguity_detection: LogLevel::Warn, - ..Default::default() - }); - }); - - (client_ent, client_helper) -} - -#[macro_export] macro_rules! assert_packet_order { ($sent_packets:ident, $($packets:pat),+) => {{ - let sent_packets: &Vec = &$sent_packets; + let sent_packets: &Vec = &$sent_packets; let positions = [ $((sent_packets.iter().position(|p| matches!(p, $packets))),)* ]; @@ -202,10 +209,9 @@ macro_rules! assert_packet_order { }}; } -#[macro_export] macro_rules! assert_packet_count { ($sent_packets:ident, $count:tt, $packet:pat) => {{ - let sent_packets: &Vec = &$sent_packets; + let sent_packets: &Vec = &$sent_packets; let count = sent_packets.iter().filter(|p| matches!(p, $packet)).count(); assert_eq!( count, @@ -222,3 +228,8 @@ macro_rules! assert_packet_count { ); }}; } + +mod client; +mod example; +mod inventory; +mod weather; diff --git a/crates/valence/src/tests/client.rs b/crates/valence/src/tests/client.rs new file mode 100644 index 0000000..f801510 --- /dev/null +++ b/crates/valence/src/tests/client.rs @@ -0,0 +1,82 @@ +use std::collections::BTreeSet; + +use bevy_app::App; +use bevy_ecs::world::EntityMut; +use valence_client::ViewDistance; +use valence_core::chunk_pos::ChunkView; +use valence_core::packet::s2c::play::{ChunkDataS2c, S2cPlayPacket, UnloadChunkS2c}; +use valence_entity::Position; +use valence_instance::Chunk; + +use super::*; + +#[test] +fn client_chunk_view_change() { + let mut app = App::new(); + + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + let mut instance = app + .world + .query::<&mut Instance>() + .single_mut(&mut app.world); + + for z in -15..15 { + for x in -15..15 { + instance.insert_chunk([x, z], Chunk::default()); + } + } + + let mut client = app.world.entity_mut(client_ent); + + client.get_mut::().unwrap().set([8.0, 0.0, 8.0]); + client.get_mut::().unwrap().set(6); + + // Tick + app.update(); + let mut client = app.world.entity_mut(client_ent); + + let mut loaded_chunks = BTreeSet::new(); + + for pkt in client_helper.collect_sent() { + if let S2cPlayPacket::ChunkDataS2c(ChunkDataS2c { pos, .. }) = pkt { + assert!(loaded_chunks.insert(pos), "({pos:?})"); + } + } + + for pos in view(&client).iter() { + assert!(loaded_chunks.contains(&pos), "{pos:?}"); + } + + assert!(!loaded_chunks.is_empty()); + + // Move the client to the adjacent chunk. + client.get_mut::().unwrap().set([24.0, 0.0, 24.0]); + + // Tick + app.update(); + let client = app.world.entity_mut(client_ent); + + for pkt in client_helper.collect_sent() { + match pkt { + S2cPlayPacket::ChunkDataS2c(ChunkDataS2c { pos, .. }) => { + assert!(loaded_chunks.insert(pos), "({pos:?})"); + } + S2cPlayPacket::UnloadChunkS2c(UnloadChunkS2c { pos }) => { + assert!(loaded_chunks.remove(&pos), "({pos:?})"); + } + _ => {} + } + } + + for pos in view(&client).iter() { + assert!(loaded_chunks.contains(&pos), "{pos:?}"); + } +} + +fn view(client: &EntityMut) -> ChunkView { + let chunk_pos = client.get::().unwrap().chunk_pos(); + let view_dist = client.get::().unwrap().get(); + + ChunkView::new(chunk_pos, view_dist) +} diff --git a/crates/valence/src/tests/example.rs b/crates/valence/src/tests/example.rs new file mode 100644 index 0000000..d5c5c34 --- /dev/null +++ b/crates/valence/src/tests/example.rs @@ -0,0 +1,88 @@ +//! Examples of valence unit tests that need to test the behavior of the server, +//! and not just the logic of a single function. This module is meant to be a +//! pallette of examples for how to write such tests, with various levels of +//! complexity. +//! +//! Some of the tests in this file may be inferior duplicates of real tests. + +use bevy_app::App; +use valence_core::packet::c2s::play::PositionAndOnGround; +use valence_core::packet::s2c::play::S2cPlayPacket; + +use super::*; +use crate::prelude::*; + +/// The server's tick should increment every update. +#[test] +fn example_test_server_tick_increment() { + let mut app = App::new(); + + app.add_plugins(DefaultPlugins); + + let tick = app.world.resource::().current_tick(); + + app.update(); + + let server = app.world.resource::(); + assert_eq!(server.current_tick(), tick + 1); +} + +/// A unit test where we want to test what happens when a client sends a +/// packet to the server. +#[test] +fn example_test_client_position() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Send a packet as the client to the server. + let packet = PositionAndOnGround { + position: DVec3::new(12.0, 64.0, 0.0), + on_ground: true, + }; + client_helper.send(&packet); + + // Process the packet. + app.update(); + + // Make assertions + let pos = app.world.get::(client_ent).unwrap(); + assert_eq!(pos.0, DVec3::new(12.0, 64.0, 0.0)); +} + +/// A unit test where we want to test what packets are sent to the client. +#[test] +fn example_test_open_inventory() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + let inventory = Inventory::new(InventoryKind::Generic3x3); + let inventory_ent = app.world.spawn(inventory).id(); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Open the inventory. + let open_inventory = OpenInventory::new(inventory_ent); + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .insert(open_inventory); + + app.update(); + app.update(); + + // Make assertions + app.world + .get::(client_ent) + .expect("client not found"); + let sent_packets = client_helper.collect_sent(); + + assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_)); + assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); + assert_packet_order!( + sent_packets, + S2cPlayPacket::OpenScreenS2c(_), + S2cPlayPacket::InventoryS2c(_) + ); +} diff --git a/crates/valence/src/tests/inventory.rs b/crates/valence/src/tests/inventory.rs new file mode 100644 index 0000000..8ce7d01 --- /dev/null +++ b/crates/valence/src/tests/inventory.rs @@ -0,0 +1,1038 @@ +use bevy_app::App; +use valence_core::game_mode::GameMode; +use valence_core::item::{ItemKind, ItemStack}; +use valence_core::packet::c2s::play::click_slot::{ClickMode, Slot}; +use valence_core::packet::c2s::play::ClickSlotC2s; +use valence_core::packet::s2c::play::S2cPlayPacket; +use valence_inventory::{ + convert_to_player_slot_id, ClientInventoryState, CursorItem, DropItemStack, Inventory, + InventoryKind, OpenInventory, +}; + +use super::*; + +#[test] +fn test_should_open_inventory() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + let inventory = Inventory::new(InventoryKind::Generic3x3); + let inventory_ent = app.world.spawn(inventory).id(); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Open the inventory. + let open_inventory = OpenInventory::new(inventory_ent); + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .insert(open_inventory); + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + + assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_)); + assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); + assert_packet_order!( + sent_packets, + S2cPlayPacket::OpenScreenS2c(_), + S2cPlayPacket::InventoryS2c(_) + ); +} + +#[test] +fn test_should_close_inventory() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + let inventory = Inventory::new(InventoryKind::Generic3x3); + let inventory_ent = app.world.spawn(inventory).id(); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Open the inventory. + let open_inventory = OpenInventory::new(inventory_ent); + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .insert(open_inventory); + + app.update(); + client_helper.clear_sent(); + + // Close the inventory. + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .remove::(); + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + + assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_)); +} + +#[test] +fn test_should_remove_invalid_open_inventory() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + let inventory = Inventory::new(InventoryKind::Generic3x3); + let inventory_ent = app.world.spawn(inventory).id(); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Open the inventory. + let open_inventory = OpenInventory::new(inventory_ent); + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .insert(open_inventory); + + app.update(); + client_helper.clear_sent(); + + // Remove the inventory. + app.world.despawn(inventory_ent); + + app.update(); + + // Make assertions + assert!(app.world.get::(client_ent).is_none()); + let sent_packets = client_helper.collect_sent(); + assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_)); +} + +#[test] +fn test_should_modify_player_inventory_click_slot() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory for client"); + inventory.set_slot(20, ItemStack::new(ItemKind::Diamond, 2, None)); + + // Make the client click the slot and pick up the item. + let state_id = app + .world + .get::(client_ent) + .unwrap() + .state_id(); + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id: 0, + button: 0, + mode: valence_core::packet::c2s::play::click_slot::ClickMode::Click, + state_id: VarInt(state_id.0), + slot_idx: 20, + slot_changes: vec![valence_core::packet::c2s::play::click_slot::Slot { + idx: 20, + item: None, + }], + carried_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), + }); + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + + // because the inventory was changed as a result of the client's click, the + // server should not send any packets to the client because the client + // already knows about the change. + assert_packet_count!( + sent_packets, + 0, + S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) + ); + let inventory = app + .world + .get::(client_ent) + .expect("could not find inventory for client"); + assert_eq!(inventory.slot(20), None); + let cursor_item = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!( + cursor_item.0, + Some(ItemStack::new(ItemKind::Diamond, 2, None)) + ); +} + +#[test] +fn test_should_modify_player_inventory_server_side() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory for client"); + inventory.set_slot(20, ItemStack::new(ItemKind::Diamond, 2, None)); + + app.update(); + client_helper.clear_sent(); + + // Modify the inventory. + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory for client"); + inventory.set_slot(21, ItemStack::new(ItemKind::IronIngot, 1, None)); + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + // because the inventory was modified server side, the client needs to be + // updated with the change. + assert_packet_count!( + sent_packets, + 1, + S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) + ); +} + +#[test] +fn test_should_sync_entire_player_inventory() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory for client"); + inventory.changed = u64::MAX; + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); +} + +fn set_up_open_inventory(app: &mut App, client_ent: Entity) -> Entity { + let inventory = Inventory::new(InventoryKind::Generic9x3); + let inventory_ent = app.world.spawn(inventory).id(); + + // Open the inventory. + let open_inventory = OpenInventory::new(inventory_ent); + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .insert(open_inventory); + + inventory_ent +} + +#[test] +fn test_should_modify_open_inventory_click_slot() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + let inventory_ent = set_up_open_inventory(&mut app, client_ent); + let mut inventory = app + .world + .get_mut::(inventory_ent) + .expect("could not find inventory for client"); + inventory.set_slot(20, ItemStack::new(ItemKind::Diamond, 2, None)); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Make the client click the slot and pick up the item. + let inv_state = app.world.get::(client_ent).unwrap(); + let state_id = inv_state.state_id(); + let window_id = inv_state.window_id(); + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id, + button: 0, + mode: valence_core::packet::c2s::play::click_slot::ClickMode::Click, + state_id: VarInt(state_id.0), + slot_idx: 20, + slot_changes: vec![valence_core::packet::c2s::play::click_slot::Slot { + idx: 20, + item: None, + }], + carried_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), + }); + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + + // because the inventory was modified as a result of the client's click, the + // server should not send any packets to the client because the client + // already knows about the change. + assert_packet_count!( + sent_packets, + 0, + S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) + ); + let inventory = app + .world + .get::(inventory_ent) + .expect("could not find inventory"); + assert_eq!(inventory.slot(20), None); + let cursor_item = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!( + cursor_item.0, + Some(ItemStack::new(ItemKind::Diamond, 2, None)) + ); +} + +#[test] +fn test_should_modify_open_inventory_server_side() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + let inventory_ent = set_up_open_inventory(&mut app, client_ent); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Modify the inventory. + let mut inventory = app + .world + .get_mut::(inventory_ent) + .expect("could not find inventory for client"); + inventory.set_slot(5, ItemStack::new(ItemKind::IronIngot, 1, None)); + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + + // because the inventory was modified server side, the client needs to be + // updated with the change. + assert_packet_count!( + sent_packets, + 1, + S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) + ); + let inventory = app + .world + .get::(inventory_ent) + .expect("could not find inventory for client"); + assert_eq!( + inventory.slot(5), + Some(&ItemStack::new(ItemKind::IronIngot, 1, None)) + ); +} + +#[test] +fn test_should_sync_entire_open_inventory() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + let inventory_ent = set_up_open_inventory(&mut app, client_ent); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let mut inventory = app + .world + .get_mut::(inventory_ent) + .expect("could not find inventory"); + inventory.changed = u64::MAX; + + app.update(); + + // Make assertions + let sent_packets = client_helper.collect_sent(); + assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); +} + +#[test] +fn test_set_creative_mode_slot_handling() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + let mut game_mode = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + *game_mode.as_mut() = GameMode::Creative; + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + client_helper.send( + &valence_core::packet::c2s::play::CreativeInventoryActionC2s { + slot: 36, + clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), + }, + ); + + app.update(); + + // Make assertions + let inventory = app + .world + .get::(client_ent) + .expect("could not find inventory for client"); + assert_eq!( + inventory.slot(36), + Some(&ItemStack::new(ItemKind::Diamond, 2, None)) + ); +} + +#[test] +fn test_ignore_set_creative_mode_slot_if_not_creative() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + let mut game_mode = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + *game_mode.as_mut() = GameMode::Survival; + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + client_helper.send( + &valence_core::packet::c2s::play::CreativeInventoryActionC2s { + slot: 36, + clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), + }, + ); + + app.update(); + + // Make assertions + let inventory = app + .world + .get::(client_ent) + .expect("could not find inventory for client"); + assert_eq!(inventory.slot(36), None); +} + +#[test] +fn test_window_id_increments() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + let inventory = Inventory::new(InventoryKind::Generic9x3); + let inventory_ent = app.world.spawn(inventory).id(); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + for _ in 0..3 { + let open_inventory = OpenInventory::new(inventory_ent); + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .insert(open_inventory); + + app.update(); + + app.world + .get_entity_mut(client_ent) + .expect("could not find client") + .remove::(); + + app.update(); + } + + // Make assertions + let inv_state = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!(inv_state.window_id(), 3); +} + +#[test] +fn test_should_handle_set_held_item() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + client_helper.send(&valence_core::packet::c2s::play::UpdateSelectedSlotC2s { slot: 4 }); + + app.update(); + + // Make assertions + let inv_state = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!(inv_state.held_item_slot(), 40); +} + +#[test] +fn should_not_increment_state_id_on_cursor_item_change() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let inv_state = app + .world + .get::(client_ent) + .expect("could not find client"); + let expected_state_id = inv_state.state_id().0; + + let mut cursor_item = app.world.get_mut::(client_ent).unwrap(); + cursor_item.0 = Some(ItemStack::new(ItemKind::Diamond, 2, None)); + + app.update(); + + // Make assertions + let inv_state = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!( + inv_state.state_id().0, + expected_state_id, + "state id should not have changed" + ); +} + +mod dropping_items { + use valence_core::block_pos::BlockPos; + use valence_core::direction::Direction; + use valence_core::packet::c2s::play::click_slot::{ClickMode, Slot}; + use valence_core::packet::c2s::play::player_action::Action; + use valence_inventory::convert_to_player_slot_id; + + use super::*; + + #[test] + fn should_drop_item_player_action() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory"); + inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 3, None)); + + client_helper.send(&valence_core::packet::c2s::play::PlayerActionC2s { + action: Action::DropItem, + position: BlockPos::new(0, 0, 0), + direction: Direction::Down, + sequence: VarInt(0), + }); + + app.update(); + + // Make assertions + let inventory = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!( + inventory.slot(36), + Some(&ItemStack::new(ItemKind::IronIngot, 2, None)) + ); + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!(events[0].from_slot, Some(36)); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 1, None) + ); + + let sent_packets = client_helper.collect_sent(); + assert_packet_count!( + sent_packets, + 0, + S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) + ); + } + + #[test] + fn should_drop_item_stack_player_action() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory"); + inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 32, None)); + + client_helper.send(&valence_core::packet::c2s::play::PlayerActionC2s { + action: Action::DropAllItems, + position: BlockPos::new(0, 0, 0), + direction: Direction::Down, + sequence: VarInt(0), + }); + + app.update(); + + // Make assertions + let inv_state = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!(inv_state.held_item_slot(), 36); + let inventory = app + .world + .get::(client_ent) + .expect("could not find inventory"); + assert_eq!(inventory.slot(36), None); + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!(events[0].from_slot, Some(36)); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 32, None) + ); + } + + #[test] + fn should_drop_item_stack_set_creative_mode_slot() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + app.world.entity_mut(client_ent).insert(GameMode::Creative); + + client_helper.send( + &valence_core::packet::c2s::play::CreativeInventoryActionC2s { + slot: -1, + clicked_item: Some(ItemStack::new(ItemKind::IronIngot, 32, None)), + }, + ); + + app.update(); + + // Make assertions + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events") + .iter_current_update_events() + .collect::>(); + + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!(events[0].from_slot, None); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 32, None) + ); + } + + #[test] + fn should_drop_item_stack_click_container_outside() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let mut cursor_item = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + cursor_item.0 = Some(ItemStack::new(ItemKind::IronIngot, 32, None)); + let inv_state = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + let state_id = inv_state.state_id().0; + + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id: 0, + slot_idx: -999, + button: 0, + mode: ClickMode::Click, + state_id: VarInt(state_id), + slot_changes: vec![], + carried_item: None, + }); + + app.update(); + + // Make assertions + let cursor_item = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!(cursor_item.0, None); + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!(events[0].from_slot, None); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 32, None) + ); + } + + #[test] + fn should_drop_item_click_container_with_dropkey_single() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let inv_state = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + let state_id = inv_state.state_id().0; + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory"); + inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None)); + + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id: 0, + slot_idx: 40, + button: 0, + mode: ClickMode::DropKey, + state_id: VarInt(state_id), + slot_changes: vec![Slot { + idx: 40, + item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)), + }], + carried_item: None, + }); + + app.update(); + + // Make assertions + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!(events[0].from_slot, Some(40)); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 1, None) + ); + } + + #[test] + fn should_drop_item_stack_click_container_with_dropkey() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + let inv_state = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + let state_id = inv_state.state_id().0; + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory"); + inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None)); + + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id: 0, + slot_idx: 40, + button: 1, // pressing control + mode: ClickMode::DropKey, + state_id: VarInt(state_id), + slot_changes: vec![Slot { + idx: 40, + item: None, + }], + carried_item: None, + }); + + app.update(); + + // Make assertions + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!(events[0].from_slot, Some(40)); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 32, None) + ); + } + + #[test] + fn should_drop_item_player_open_inventory_with_dropkey() { + // The item should be dropped successfully, if the player has an inventory open + // and the slot id points to his inventory. + + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory"); + inventory.set_slot( + convert_to_player_slot_id(InventoryKind::Generic9x3, 50), + ItemStack::new(ItemKind::IronIngot, 32, None), + ); + let _inventory_ent = set_up_open_inventory(&mut app, client_ent); + + app.update(); + client_helper.clear_sent(); + + let inv_state = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + + let state_id = inv_state.state_id().0; + let window_id = inv_state.window_id(); + + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id, + slot_idx: 50, + button: 0, // not pressing control + mode: ClickMode::DropKey, + state_id: VarInt(state_id), + slot_changes: vec![Slot { + idx: 50, + item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)), + }], + carried_item: None, + }); + + app.update(); + + // Make assertions + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let player_inventory = app + .world + .get::(client_ent) + .expect("could not find inventory"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!( + events[0].from_slot, + Some(convert_to_player_slot_id(InventoryKind::Generic9x3, 50)) + ); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 1, None) + ); + // Also make sure that the player inventory was updated correctly. + let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50); + assert_eq!( + player_inventory.slot(expected_player_slot_id), + Some(&ItemStack::new(ItemKind::IronIngot, 31, None)) + ); + } +} + +#[test] +fn should_drop_item_stack_player_open_inventory_with_dropkey() { + // The item stack should be dropped successfully, if the player has an inventory + // open and the slot id points to his inventory. + + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + + let mut inventory = app + .world + .get_mut::(client_ent) + .expect("could not find inventory"); + inventory.set_slot( + convert_to_player_slot_id(InventoryKind::Generic9x3, 50), + ItemStack::new(ItemKind::IronIngot, 32, None), + ); + let _inventory_ent = set_up_open_inventory(&mut app, client_ent); + + app.update(); + client_helper.clear_sent(); + + let inv_state = app + .world + .get_mut::(client_ent) + .expect("could not find client"); + + let state_id = inv_state.state_id().0; + let window_id = inv_state.window_id(); + + client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { + window_id, + slot_idx: 50, + button: 1, // pressing control, the whole stack is dropped + mode: ClickMode::DropKey, + state_id: VarInt(state_id), + slot_changes: vec![Slot { + idx: 50, + item: None, + }], + carried_item: None, + }); + + app.update(); + + // Make assertions + let events = app + .world + .get_resource::>() + .expect("expected drop item stack events"); + let player_inventory = app + .world + .get::(client_ent) + .expect("could not find inventory"); + let events = events.iter_current_update_events().collect::>(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].client, client_ent); + assert_eq!( + events[0].from_slot, + Some(convert_to_player_slot_id(InventoryKind::Generic9x3, 50)) + ); + assert_eq!( + events[0].stack, + ItemStack::new(ItemKind::IronIngot, 32, None) + ); + // Also make sure that the player inventory was updated correctly. + let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50); + assert_eq!(player_inventory.slot(expected_player_slot_id), None); +} + +#[test] +fn dragging_items() { + let mut app = App::new(); + let (client_ent, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + app.world.get_mut::(client_ent).unwrap().0 = + Some(ItemStack::new(ItemKind::Diamond, 64, None)); + + let inv_state = app.world.get::(client_ent).unwrap(); + let window_id = inv_state.window_id(); + let state_id = inv_state.state_id().0; + + let drag_packet = ClickSlotC2s { + window_id, + state_id: VarInt(state_id), + slot_idx: -999, + button: 2, + mode: ClickMode::Drag, + slot_changes: vec![ + Slot { + idx: 9, + item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), + }, + Slot { + idx: 10, + item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), + }, + Slot { + idx: 11, + item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), + }, + ], + carried_item: Some(ItemStack::new(ItemKind::Diamond, 1, None)), + }; + client_helper.send(&drag_packet); + + app.update(); + let sent_packets = client_helper.collect_sent(); + assert_eq!(sent_packets.len(), 0); + + let cursor_item = app + .world + .get::(client_ent) + .expect("could not find client"); + assert_eq!( + cursor_item.0, + Some(ItemStack::new(ItemKind::Diamond, 1, None)) + ); + let inventory = app + .world + .get::(client_ent) + .expect("could not find inventory"); + for i in 9..12 { + assert_eq!( + inventory.slot(i), + Some(&ItemStack::new(ItemKind::Diamond, 21, None)) + ); + } +} diff --git a/crates/valence/src/tests/weather.rs b/crates/valence/src/tests/weather.rs new file mode 100644 index 0000000..dc6822f --- /dev/null +++ b/crates/valence/src/tests/weather.rs @@ -0,0 +1,141 @@ +use bevy_app::App; +use valence_client::weather::{Rain, Thunder}; +use valence_client::Client; +use valence_core::packet::s2c::play::game_state_change::GameEventKind; +use valence_core::packet::s2c::play::{GameStateChangeS2c, S2cPlayPacket}; + +use super::*; + +fn assert_weather_packets(sent_packets: Vec) { + assert_packet_count!(sent_packets, 6, S2cPlayPacket::GameStateChangeS2c(_)); + + assert_packet_order!( + sent_packets, + S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { + kind: GameEventKind::BeginRaining, + value: _ + }), + S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { + kind: GameEventKind::RainLevelChange, + value: _ + }), + S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: _ + }), + S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { + kind: GameEventKind::EndRaining, + value: _ + }) + ); + + if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[1] { + assert_eq!(pkt.value, 0.5); + } + + if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[2] { + assert_eq!(pkt.value, 1.0); + } + + if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[3] { + assert_eq!(pkt.value, 0.5); + } + + if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[4] { + assert_eq!(pkt.value, 1.0); + } +} + +#[test] +fn test_weather_instance() { + let mut app = App::new(); + let (_, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Get the instance entity. + let instance_ent = app + .world + .iter_entities() + .find(|e| e.contains::()) + .expect("could not find instance") + .id(); + + // Insert a rain component to the instance. + app.world.entity_mut(instance_ent).insert(Rain(0.5)); + for _ in 0..2 { + app.update(); + } + + // Alter a rain component of the instance. + app.world.entity_mut(instance_ent).insert(Rain(1.0)); + app.update(); + + // Insert a thunder component to the instance. + app.world.entity_mut(instance_ent).insert(Thunder(0.5)); + app.update(); + + // Alter a thunder component of the instance. + app.world.entity_mut(instance_ent).insert(Thunder(1.0)); + app.update(); + + // Remove the rain component from the instance. + app.world.entity_mut(instance_ent).remove::(); + for _ in 0..2 { + app.update(); + } + + // Make assertions. + let sent_packets = client_helper.collect_sent(); + + assert_weather_packets(sent_packets); +} + +#[test] +fn test_weather_client() { + let mut app = App::new(); + let (_, mut client_helper) = scenario_single_client(&mut app); + + // Process a tick to get past the "on join" logic. + app.update(); + client_helper.clear_sent(); + + // Get the client entity. + let client_ent = app + .world + .iter_entities() + .find(|e| e.contains::()) + .expect("could not find client") + .id(); + + // Insert a rain component to the client. + app.world.entity_mut(client_ent).insert(Rain(0.5)); + for _ in 0..2 { + app.update(); + } + + // Alter a rain component of the client. + app.world.entity_mut(client_ent).insert(Rain(1.0)); + app.update(); + + // Insert a thunder component to the client. + app.world.entity_mut(client_ent).insert(Thunder(0.5)); + app.update(); + + // Alter a thunder component of the client. + app.world.entity_mut(client_ent).insert(Thunder(1.0)); + app.update(); + + // Remove the rain component from the client. + app.world.entity_mut(client_ent).remove::(); + for _ in 0..2 { + app.update(); + } + + // Make assertions. + let sent_packets = client_helper.collect_sent(); + + assert_weather_packets(sent_packets); +} diff --git a/crates/valence/src/unit_test/example.rs b/crates/valence/src/unit_test/example.rs deleted file mode 100644 index 98ac16b..0000000 --- a/crates/valence/src/unit_test/example.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! # Unit Test Cookbook -//! -//! Setting up an `App` with a single client: -//! ```ignore -//! # use bevy_app::App; -//! # use valence::unit_test::util::scenario_single_client; -//! let mut app = App::new(); -//! let (client_ent, mut client_helper) = scenario_single_client(&mut app); -//! ``` -//! -//! Asserting packets sent to the client: -//! ```ignore -//! # use bevy_app::App; -//! # use valence::unit_test::util::scenario_single_client; -//! # use valence::client::Client; -//! # fn main() -> anyhow::Result<()> { -//! # let mut app = App::new(); -//! # let (client_ent, mut client_helper) = scenario_single_client(&mut app); -//! # let client: &Client = app.world.get(client_ent).expect("client not found"); -//! client.write_packet(&valence_protocol::packets::s2c::play::KeepAliveS2c { id: 0xdeadbeef }); -//! client.write_packet(&valence_protocol::packets::s2c::play::KeepAliveS2c { id: 0xf00dcafe }); -//! -//! let sent_packets = client_helper.collect_sent()?; -//! assert_packet_count!(sent_packets, 2, S2cPlayPacket::KeepAliveS2c(_)); -//! assert_packet_order!( -//! sent_packets, -//! S2cPlayPacket::KeepAliveS2c(KeepAliveS2c { id: 0xdeadbeef }), -//! S2cPlayPacket::KeepAliveS2c(KeepAliveS2c { id: 0xf00dcafe }), -//! ); -//! # Ok(()) -//! # } -//! ``` -//! -//! Performing a Query without a system is possible, like so: -//! ``` -//! # use bevy_app::App; -//! # use valence::instance::Instance; -//! # let mut app = App::new(); -//! app.world.query::<&Instance>(); -//! ``` - -use bevy_app::App; - -use crate::config::ServerPlugin; -use crate::server::Server; -use crate::unit_test::util::scenario_single_client; - -/// Examples of valence unit tests that need to test the behavior of the server, -/// and not just the logic of a single function. This module is meant to be a -/// pallette of examples for how to write such tests, with various levels of -/// complexity. -/// -/// Some of the tests in this file may be inferior duplicates of real tests. -#[cfg(test)] -mod tests { - use valence_protocol::packet::S2cPlayPacket; - - use super::*; - use crate::client::Client; - use crate::component::Position; - use crate::inventory::{Inventory, InventoryKind, OpenInventory}; - use crate::{assert_packet_count, assert_packet_order}; - - /// The server's tick should increment every update. - #[test] - fn example_test_server_tick_increment() { - let mut app = App::new(); - app.add_plugin(ServerPlugin::new(())); - let server = app.world.resource::(); - let tick = server.current_tick(); - app.update(); - let server = app.world.resource::(); - assert_eq!(server.current_tick(), tick + 1); - } - - /// A unit test where we want to test what happens when a client sends a - /// packet to the server. - #[test] - fn example_test_client_position() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - // Send a packet as the client to the server. - let packet = valence_protocol::packet::c2s::play::PositionAndOnGround { - position: [12.0, 64.0, 0.0], - on_ground: true, - }; - client_helper.send(&packet); - - // Process the packet. - app.update(); - - // Make assertions - let pos = app.world.get::(client_ent).unwrap(); - assert_eq!(pos.0, [12.0, 64.0, 0.0].into()); - } - - /// A unit test where we want to test what packets are sent to the client. - #[test] - fn example_test_open_inventory() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - let inventory = Inventory::new(InventoryKind::Generic3x3); - let inventory_ent = app.world.spawn(inventory).id(); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Open the inventory. - let open_inventory = OpenInventory::new(inventory_ent); - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .insert(open_inventory); - - app.update(); - app.update(); - - // Make assertions - app.world - .get::(client_ent) - .expect("client not found"); - let sent_packets = client_helper.collect_sent(); - - assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_)); - assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); - assert_packet_order!( - sent_packets, - S2cPlayPacket::OpenScreenS2c(_), - S2cPlayPacket::InventoryS2c(_) - ); - } -} diff --git a/crates/valence/src/unit_test/mod.rs b/crates/valence/src/unit_test/mod.rs deleted file mode 100644 index 6476440..0000000 --- a/crates/valence/src/unit_test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod example; -pub(crate) mod util; diff --git a/crates/valence/src/util.rs b/crates/valence/src/util.rs deleted file mode 100644 index 50369c1..0000000 --- a/crates/valence/src/util.rs +++ /dev/null @@ -1,123 +0,0 @@ -pub use glam::*; - -use crate::config::DEFAULT_TPS; - -/// An axis-aligned bounding box. `min` is expected to be <= `max` -/// componentwise. -#[derive(Copy, Clone, PartialEq, Default, Debug)] -pub struct Aabb { - pub min: DVec3, - pub max: DVec3, -} - -impl Aabb { - pub fn new(p0: impl Into, p1: impl Into) -> Self { - let p0 = p0.into(); - let p1 = p1.into(); - Self { - min: p0.min(p1), - max: p0.max(p1), - } - } - - #[allow(dead_code)] - pub(crate) fn from_bottom_size(bottom: impl Into, size: impl Into) -> Self { - let bottom = bottom.into(); - let size = size.into(); - - Self { - min: DVec3 { - x: bottom.x - size.x / 2.0, - y: bottom.y, - z: bottom.z - size.z / 2.0, - }, - max: DVec3 { - x: bottom.x + size.x / 2.0, - y: bottom.y + size.y, - z: bottom.z + size.z / 2.0, - }, - } - } -} - -/// Takes a normalized direction vector and returns a `(yaw, pitch)` tuple in -/// degrees. -/// -/// This function is the inverse of [`from_yaw_and_pitch`] except for the case -/// where the direction is straight up or down. -#[track_caller] -pub fn to_yaw_and_pitch(d: Vec3) -> (f32, f32) { - debug_assert!(d.is_normalized(), "the given vector should be normalized"); - - let yaw = f32::atan2(d.z, d.x).to_degrees() - 90.0; - let pitch = -(d.y).asin().to_degrees(); - (yaw, pitch) -} - -/// Takes yaw and pitch angles (in degrees) and returns a normalized -/// direction vector. -/// -/// This function is the inverse of [`to_yaw_and_pitch`]. -pub fn from_yaw_and_pitch(yaw: f32, pitch: f32) -> Vec3 { - let (yaw_sin, yaw_cos) = (yaw + 90.0).to_radians().sin_cos(); - let (pitch_sin, pitch_cos) = (-pitch).to_radians().sin_cos(); - - Vec3::new(yaw_cos * pitch_cos, pitch_sin, yaw_sin * pitch_cos) -} - -/// Returns the minimum number of bits needed to represent the integer `n`. -pub(crate) const fn bit_width(n: usize) -> usize { - (usize::BITS - n.leading_zeros()) as _ -} - -/// Returns whether or not the given string is a valid Minecraft username. -/// -/// A valid username is 3 to 16 characters long with only ASCII alphanumeric -/// characters. The username must match the regex `^[a-zA-Z0-9_]{3,16}$` to be -/// considered valid. -/// -/// # Examples -/// -/// ``` -/// use valence::util::is_valid_username; -/// -/// assert!(is_valid_username("00a")); -/// assert!(is_valid_username("jeb_")); -/// -/// assert!(!is_valid_username("notavalidusername")); -/// assert!(!is_valid_username("NotValid!")); -/// ``` -pub fn is_valid_username(username: &str) -> bool { - (3..=16).contains(&username.len()) - && username - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '_') -} - -#[cfg(test)] -mod tests { - use approx::assert_relative_eq; - use rand::random; - - use super::*; - - #[test] - fn yaw_pitch_round_trip() { - for _ in 0..=100 { - let d = (Vec3::new(random(), random(), random()) * 2.0 - 1.0).normalize(); - - let (yaw, pitch) = to_yaw_and_pitch(d); - let d_new = from_yaw_and_pitch(yaw, pitch); - - assert_relative_eq!(d, d_new, epsilon = f32::EPSILON * 100.0); - } - } -} - -#[inline] -pub(crate) fn velocity_to_packet_units(vel: Vec3) -> [i16; 3] { - // The saturating casts to i16 are desirable. - (8000.0 / DEFAULT_TPS as f32 * vel) - .to_array() - .map(|v| v as i16) -} diff --git a/crates/valence/src/weather.rs b/crates/valence/src/weather.rs deleted file mode 100644 index bd27866..0000000 --- a/crates/valence/src/weather.rs +++ /dev/null @@ -1,413 +0,0 @@ -//! The weather system. -//! -//! This module contains the systems and components needed to handle -//! weather. -//! -//! # Components -//! -//! The components may be attached to clients or instances. -//! -//! - [`Rain`]: When attached, raining begin and rain level set events are -//! emitted. When removed, the end raining event is emitted. -//! - [`Thunder`]: When attached, thunder level set event is emitted. When -//! removed, the thunder level set to zero event is emitted. -//! -//! New joined players are handled, so that they are get weather events from -//! the instance. - -use std::ops::Range; - -use bevy_ecs::prelude::*; -use valence_protocol::packet::s2c::play::game_state_change::GameEventKind; -use valence_protocol::packet::s2c::play::GameStateChangeS2c; - -use crate::client::FlushPacketsSet; -use crate::instance::WriteUpdatePacketsToInstancesSet; -use crate::packet::WritePacket; -use crate::prelude::*; - -pub const WEATHER_LEVEL: Range = 0_f32..1_f32; - -/// Contains the rain level. -/// Valid value is a value within the [WEATHER_LEVEL] range. -/// Invalid value would be clamped. -#[derive(Component)] -pub struct Rain(pub f32); - -/// Contains the thunder level. -/// Valid value is a value within the [WEATHER_LEVEL] range. -/// Invalid value would be clamped. -#[derive(Component)] -pub struct Thunder(pub f32); - -impl Instance { - /// Sends the begin rain event to all players in the instance. - fn begin_raining(&mut self) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::BeginRaining, - value: f32::default(), - }); - } - - /// Sends the end rain event to all players in the instance. - fn end_raining(&mut self) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::EndRaining, - value: f32::default(), - }); - } - - /// Sends the set rain level event to all players in the instance. - fn set_rain_level(&mut self, level: f32) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::RainLevelChange, - value: level.clamp(WEATHER_LEVEL.start, WEATHER_LEVEL.end), - }); - } - - /// Sends the set thunder level event to all players in the instance. - fn set_thunder_level(&mut self, level: f32) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::ThunderLevelChange, - value: level.clamp(WEATHER_LEVEL.start, WEATHER_LEVEL.end), - }); - } -} - -impl Client { - /// Sends the begin rain event to the client. - fn begin_raining(&mut self) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::BeginRaining, - value: f32::default(), - }); - } - - /// Sends the end rain event to the client. - fn end_raining(&mut self) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::EndRaining, - value: f32::default(), - }); - } - - /// Sends the set rain level event to the client. - fn set_rain_level(&mut self, level: f32) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::RainLevelChange, - value: level.clamp(WEATHER_LEVEL.start, WEATHER_LEVEL.end), - }); - } - - /// Sends the set thunder level event to the client. - fn set_thunder_level(&mut self, level: f32) { - self.write_packet(&GameStateChangeS2c { - kind: GameEventKind::ThunderLevelChange, - value: level.clamp(WEATHER_LEVEL.start, WEATHER_LEVEL.end), - }); - } -} - -fn handle_weather_for_joined_player( - mut clients: Query<(&mut Client, &Location), Added>, - weathers: Query<(Option<&Rain>, Option<&Thunder>), With>, -) { - clients.for_each_mut(|(mut client, loc)| { - if let Ok((rain, thunder)) = weathers.get(loc.0) { - if let Some(level) = rain { - client.begin_raining(); - client.set_rain_level(level.0); - } - - if let Some(level) = thunder { - client.set_thunder_level(level.0); - } - } - }) -} - -fn handle_rain_begin_per_instance(mut query: Query<&mut Instance, Added>) { - query.for_each_mut(|mut instance| { - instance.begin_raining(); - }); -} - -fn handle_rain_change_per_instance(mut query: Query<(&mut Instance, &Rain), Changed>) { - query.for_each_mut(|(mut instance, rain)| instance.set_rain_level(rain.0)); -} - -fn handle_rain_end_per_instance( - mut query: Query<&mut Instance>, - mut removed: RemovedComponents, -) { - removed.iter().for_each(|entity| { - if let Ok(mut instance) = query.get_mut(entity) { - instance.end_raining(); - } - }) -} - -fn handle_thunder_change_per_instance( - mut query: Query<(&mut Instance, &Thunder), Changed>, -) { - query.for_each_mut(|(mut instance, thunder)| instance.set_thunder_level(thunder.0)); -} - -fn handle_thunder_end_per_instance( - mut query: Query<&mut Instance>, - mut removed: RemovedComponents, -) { - removed.iter().for_each(|entity| { - if let Ok(mut instance) = query.get_mut(entity) { - instance.set_thunder_level(WEATHER_LEVEL.start); - } - }) -} - -fn handle_rain_begin_per_client(mut query: Query<&mut Client, (Added, Without)>) { - query.for_each_mut(|mut client| { - client.begin_raining(); - }); -} - -fn handle_rain_change_per_client( - mut query: Query<(&mut Client, &Rain), (Changed, Without)>, -) { - query.for_each_mut(|(mut client, rain)| { - client.set_rain_level(rain.0); - }); -} - -fn handle_rain_end_per_client(mut query: Query<&mut Client>, mut removed: RemovedComponents) { - removed.iter().for_each(|entity| { - if let Ok(mut client) = query.get_mut(entity) { - client.end_raining(); - } - }) -} - -fn handle_thunder_change_per_client( - mut query: Query<(&mut Client, &Thunder), (Changed, Without)>, -) { - query.for_each_mut(|(mut client, thunder)| { - client.set_thunder_level(thunder.0); - }); -} - -fn handle_thunder_end_per_client( - mut query: Query<&mut Client, Without>, - mut removed: RemovedComponents, -) { - removed.iter().for_each(|entity| { - if let Ok(mut client) = query.get_mut(entity) { - client.set_thunder_level(WEATHER_LEVEL.start); - } - }) -} - -pub(crate) struct WeatherPlugin; - -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub(crate) struct UpdateWeatherPerInstanceSet; - -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub(crate) struct UpdateWeatherPerClientSet; - -impl Plugin for WeatherPlugin { - fn build(&self, app: &mut App) { - app.configure_set( - UpdateWeatherPerInstanceSet - .in_base_set(CoreSet::PostUpdate) - .before(WriteUpdatePacketsToInstancesSet), - ) - .configure_set( - UpdateWeatherPerClientSet - .in_base_set(CoreSet::PostUpdate) - .before(FlushPacketsSet), - ) - .add_systems( - ( - handle_rain_begin_per_instance, - handle_rain_change_per_instance, - handle_rain_end_per_instance, - handle_thunder_change_per_instance, - handle_thunder_end_per_instance, - ) - .chain() - .in_set(UpdateWeatherPerInstanceSet) - .before(UpdateWeatherPerClientSet), - ) - .add_systems( - ( - handle_rain_begin_per_client, - handle_rain_change_per_client, - handle_rain_end_per_client, - handle_thunder_change_per_client, - handle_thunder_end_per_client, - ) - .chain() - .in_set(UpdateWeatherPerClientSet), - ) - .add_system( - handle_weather_for_joined_player - .before(UpdateWeatherPerClientSet) - .in_base_set(CoreSet::PostUpdate), - ); - } -} - -#[cfg(test)] -mod test { - use bevy_app::App; - use valence_protocol::packet::S2cPlayPacket; - - use super::*; - use crate::unit_test::util::scenario_single_client; - use crate::{assert_packet_count, assert_packet_order}; - - fn assert_weather_packets(sent_packets: Vec) { - assert_packet_count!(sent_packets, 6, S2cPlayPacket::GameStateChangeS2c(_)); - - assert_packet_order!( - sent_packets, - S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { - kind: GameEventKind::BeginRaining, - value: _ - }), - S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { - kind: GameEventKind::RainLevelChange, - value: _ - }), - S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { - kind: GameEventKind::ThunderLevelChange, - value: _ - }), - S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c { - kind: GameEventKind::EndRaining, - value: _ - }) - ); - - if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[1] { - assert_eq!(pkt.value, 0.5f32); - } - - if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[2] { - assert_eq!(pkt.value, WEATHER_LEVEL.end); - } - - if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[3] { - assert_eq!(pkt.value, 0.5f32); - } - - if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[4] { - assert_eq!(pkt.value, WEATHER_LEVEL.end); - } - } - - #[test] - fn test_weather_instance() { - let mut app = App::new(); - let (_, mut client_helper) = scenario_single_client(&mut app); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Get the instance entity. - let instance_ent = app - .world - .iter_entities() - .find(|e| e.contains::()) - .expect("could not find instance") - .id(); - - // Insert a rain component to the instance. - app.world.entity_mut(instance_ent).insert(Rain(0.5f32)); - for _ in 0..2 { - app.update(); - } - - // Alter a rain component of the instance. - app.world.entity_mut(instance_ent).insert(Rain( - // Invalid value to assert it is clamped. - WEATHER_LEVEL.end + 1_f32, - )); - app.update(); - - // Insert a thunder component to the instance. - app.world.entity_mut(instance_ent).insert(Thunder(0.5f32)); - app.update(); - - // Alter a thunder component of the instance. - app.world.entity_mut(instance_ent).insert(Thunder( - // Invalid value to assert it is clamped. - WEATHER_LEVEL.end + 1_f32, - )); - app.update(); - - // Remove the rain component from the instance. - app.world.entity_mut(instance_ent).remove::(); - for _ in 0..2 { - app.update(); - } - - // Make assertions. - let sent_packets = client_helper.collect_sent(); - - assert_weather_packets(sent_packets); - } - - #[test] - fn test_weather_client() { - let mut app = App::new(); - let (_, mut client_helper) = scenario_single_client(&mut app); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Get the client entity. - let client_ent = app - .world - .iter_entities() - .find(|e| e.contains::()) - .expect("could not find client") - .id(); - - // Insert a rain component to the client. - app.world.entity_mut(client_ent).insert(Rain(0.5f32)); - for _ in 0..2 { - app.update(); - } - - // Alter a rain component of the client. - app.world.entity_mut(client_ent).insert(Rain( - // Invalid value to assert it is clamped. - WEATHER_LEVEL.end + 1_f32, - )); - app.update(); - - // Insert a thunder component to the client. - app.world.entity_mut(client_ent).insert(Thunder(0.5f32)); - app.update(); - - // Alter a thunder component of the client. - app.world.entity_mut(client_ent).insert(Thunder( - // Invalid value to assert it is clamped. - WEATHER_LEVEL.end + 1_f32, - )); - app.update(); - - // Remove the rain component from the client. - app.world.entity_mut(client_ent).remove::(); - for _ in 0..2 { - app.update(); - } - - // Make assertions. - let sent_packets = client_helper.collect_sent(); - - assert_weather_packets(sent_packets); - } -} diff --git a/crates/valence_anvil/Cargo.toml b/crates/valence_anvil/Cargo.toml index cd3941b..5c5319f 100644 --- a/crates/valence_anvil/Cargo.toml +++ b/crates/valence_anvil/Cargo.toml @@ -1,45 +1,21 @@ [package] name = "valence_anvil" description = "A library for Minecraft's Anvil world format." -documentation = "https://docs.rs/valence_anvil/" +documentation.workspace = true repository = "https://github.com/valence-rs/valence/tree/main/crates/valence_anvil" readme = "README.md" -license = "MIT" +license.workspace = true keywords = ["anvil", "minecraft", "deserialization"] -version = "0.1.0" -authors = ["Ryan Johnson ", "TerminatorNL "] -edition = "2021" +version.workspace = true +edition.workspace = true [dependencies] -byteorder = "1.4.3" -flate2 = "1.0.25" -thiserror = "1.0.37" -num-integer = "0.1.45" # TODO: remove when div_ceil is stabilized. -valence = { version = "0.2.0", path = "../valence", optional = true } -valence_nbt = { version = "0.5.0", path = "../valence_nbt" } - -[dev-dependencies] -anyhow = "1.0.68" -bevy_ecs = "0.10" -clap = { version = "4.1.4", features = ["derive"] } -criterion = "0.4.0" -flume = "0.10.14" -fs_extra = "1.2.0" -tempfile = "3.3.0" -tracing = "0.1.37" -tracing-subscriber = "0.3.16" -zip = "0.6.3" -valence_protocol = { version = "0.1.0", path = "../valence_protocol" } - -[dev-dependencies.reqwest] -version = "0.11.12" -default-features = false -# Avoid OpenSSL dependency on Linux. -features = ["rustls-tls", "blocking", "stream"] - -[[bench]] -name = "world_parsing" -harness = false - -[features] -default = ["valence"] +byteorder.workspace = true +flate2.workspace = true +num-integer.workspace = true +thiserror.workspace = true +valence_biome.workspace = true +valence_block.workspace = true +valence_core.workspace = true +valence_instance.workspace = true +valence_nbt.workspace = true diff --git a/crates/valence_anvil/README.md b/crates/valence_anvil/README.md new file mode 100644 index 0000000..8c8363d --- /dev/null +++ b/crates/valence_anvil/README.md @@ -0,0 +1,3 @@ +# valence_anvil + +Support for Minecraft's [anvil file format](https://minecraft.fandom.com/wiki/Anvil_file_format). diff --git a/crates/valence_anvil/src/lib.rs b/crates/valence_anvil/src/lib.rs index 4f3e686..2b635ee 100644 --- a/crates/valence_anvil/src/lib.rs +++ b/crates/valence_anvil/src/lib.rs @@ -1,3 +1,22 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::fs::File; @@ -8,11 +27,9 @@ use std::path::PathBuf; use byteorder::{BigEndian, ReadBytesExt}; use flate2::bufread::{GzDecoder, ZlibDecoder}; use thiserror::Error; -#[cfg(feature = "valence")] pub use to_valence::*; use valence_nbt::Compound; -#[cfg(feature = "valence")] mod to_valence; #[derive(Debug)] diff --git a/crates/valence_anvil/src/to_valence.rs b/crates/valence_anvil/src/to_valence.rs index 6f5545e..ed4ab17 100644 --- a/crates/valence_anvil/src/to_valence.rs +++ b/crates/valence_anvil/src/to_valence.rs @@ -2,10 +2,10 @@ use std::borrow::Cow; use num_integer::{div_ceil, Integer}; use thiserror::Error; -use valence::biome::BiomeId; -use valence::instance::{BlockEntity, Chunk}; -use valence::protocol::block::{BlockEntityKind, BlockKind, PropName, PropValue}; -use valence::protocol::ident::Ident; +use valence_biome::BiomeId; +use valence_block::{BlockEntityKind, BlockKind, PropName, PropValue}; +use valence_core::ident::Ident; +use valence_instance::{BlockEntity, Chunk}; use valence_nbt::{Compound, List, Value}; #[derive(Clone, Debug, Error)] diff --git a/crates/valence_biome/Cargo.toml b/crates/valence_biome/Cargo.toml new file mode 100644 index 0000000..c985f28 --- /dev/null +++ b/crates/valence_biome/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "valence_biome" +version.workspace = true +edition.workspace = true + +[dependencies] +bevy_app.workspace = true +bevy_ecs.workspace = true +anyhow.workspace = true +tracing.workspace = true +valence_nbt.workspace = true +valence_registry.workspace = true +valence_core.workspace = true diff --git a/crates/valence_biome/README.md b/crates/valence_biome/README.md new file mode 100644 index 0000000..71d45b8 --- /dev/null +++ b/crates/valence_biome/README.md @@ -0,0 +1,10 @@ +# valence_biome + +Contains biomes and the biome registry. Minecraft's default biomes are added to the registry by default. + +### **NOTE:** +- Modifying the biome registry after the server has started can +break invariants within instances and clients! Make sure there are no +instances or clients spawned before mutating. +- A biome named "minecraft:plains" must exist. Otherwise, vanilla clients + will be disconnected. diff --git a/crates/valence/src/biome.rs b/crates/valence_biome/src/lib.rs similarity index 89% rename from crates/valence/src/biome.rs rename to crates/valence_biome/src/lib.rs index c1658f9..6ce3eea 100644 --- a/crates/valence/src/biome.rs +++ b/crates/valence_biome/src/lib.rs @@ -1,102 +1,53 @@ -//! Biome configuration and identification. -//! -//! **NOTE:** -//! -//! - Modifying the biome registry after the server has started can -//! break invariants within instances and clients! Make sure there are no -//! instances or clients spawned before mutating. -//! - A biome named "minecraft:plains" must exist. Otherwise, vanilla clients -//! will be disconnected. A biome named "minecraft:plains" is added by -//! default. +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] use std::ops::Index; use anyhow::{bail, Context}; -use bevy_app::{CoreSet, Plugin, StartupSet}; +use bevy_app::prelude::*; use bevy_ecs::prelude::*; use tracing::error; +use valence_core::ident; +use valence_core::ident::Ident; use valence_nbt::{compound, Value}; -use valence_protocol::ident; -use valence_protocol::ident::Ident; +use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue}; -use crate::registry_codec::{RegistryCodec, RegistryCodecSet, RegistryValue}; +pub struct BiomePlugin; -#[derive(Resource)] -pub struct BiomeRegistry { - id_to_biome: Vec, -} +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -impl BiomeRegistry { - pub const KEY: Ident<&str> = ident!("minecraft:worldgen/biome"); - - pub fn get_by_id(&self, id: BiomeId) -> Option { - self.id_to_biome.get(id.0 as usize).cloned() - } - - pub fn iter(&self) -> impl Iterator + '_ { - self.id_to_biome - .iter() - .enumerate() - .map(|(id, biome)| (BiomeId(id as _), *biome)) - } -} - -impl Index for BiomeRegistry { - type Output = Entity; - - fn index(&self, index: BiomeId) -> &Self::Output { - self.id_to_biome - .get(index.0 as usize) - .unwrap_or_else(|| panic!("invalid {index:?}")) - } -} - -/// An index into the biome registry. -#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct BiomeId(pub u16); - -#[derive(Component, Clone, Debug)] -pub struct Biome { - pub name: Ident, - pub downfall: f32, - pub fog_color: i32, - pub sky_color: i32, - pub water_color: i32, - pub water_fog_color: i32, - pub grass_color: Option, - pub has_precipitation: bool, - pub temperature: f32, - // TODO: more stuff. -} - -impl Default for Biome { - fn default() -> Self { - Self { - name: ident!("plains").into(), - downfall: 0.4, - fog_color: 12638463, - sky_color: 7907327, - water_color: 4159204, - water_fog_color: 329011, - grass_color: None, - has_precipitation: true, - temperature: 0.8, - } - } -} - -pub(crate) struct BiomePlugin; +struct BiomeSet; impl Plugin for BiomePlugin { fn build(&self, app: &mut bevy_app::App) { app.insert_resource(BiomeRegistry { id_to_biome: vec![], }) + .configure_set( + BiomeSet + .in_base_set(CoreSet::PostUpdate) + .before(RegistryCodecSet), + ) .add_systems( (update_biome_registry, remove_biomes_from_registry) .chain() - .in_base_set(CoreSet::PostUpdate) - .before(RegistryCodecSet), + .in_set(BiomeSet), ) .add_startup_system(load_default_biomes.in_base_set(StartupSet::PreStartup)); } @@ -238,3 +189,67 @@ fn remove_biomes_from_registry( } } } + +#[derive(Resource)] +pub struct BiomeRegistry { + id_to_biome: Vec, +} + +impl BiomeRegistry { + pub const KEY: Ident<&str> = ident!("minecraft:worldgen/biome"); + + pub fn get_by_id(&self, id: BiomeId) -> Option { + self.id_to_biome.get(id.0 as usize).cloned() + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.id_to_biome + .iter() + .enumerate() + .map(|(id, biome)| (BiomeId(id as _), *biome)) + } +} + +impl Index for BiomeRegistry { + type Output = Entity; + + fn index(&self, index: BiomeId) -> &Self::Output { + self.id_to_biome + .get(index.0 as usize) + .unwrap_or_else(|| panic!("invalid {index:?}")) + } +} + +/// An index into the biome registry. +#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct BiomeId(pub u16); + +#[derive(Component, Clone, Debug)] +pub struct Biome { + pub name: Ident, + pub downfall: f32, + pub fog_color: i32, + pub sky_color: i32, + pub water_color: i32, + pub water_fog_color: i32, + pub grass_color: Option, + pub has_precipitation: bool, + pub temperature: f32, + // TODO: more stuff. +} + +impl Default for Biome { + fn default() -> Self { + Self { + name: ident!("plains").into(), + downfall: 0.4, + fog_color: 12638463, + sky_color: 7907327, + water_color: 4159204, + water_fog_color: 329011, + grass_color: None, + has_precipitation: true, + temperature: 0.8, + } + } +} diff --git a/crates/valence_block/Cargo.toml b/crates/valence_block/Cargo.toml new file mode 100644 index 0000000..0a315df --- /dev/null +++ b/crates/valence_block/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "valence_block" +version.workspace = true +edition.workspace = true + +[dependencies] +valence_core.workspace = true +anyhow.workspace = true + +[build-dependencies] +anyhow.workspace = true +heck.workspace = true +proc-macro2.workspace = true +quote.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +valence_build_utils.workspace = true +valence_core.workspace = true diff --git a/crates/valence_block/README.md b/crates/valence_block/README.md new file mode 100644 index 0000000..e4db85b --- /dev/null +++ b/crates/valence_block/README.md @@ -0,0 +1,3 @@ +# valence_block + +Everything related to Minecraft blocks. Primarily concerned with the [`BlockState`] type. diff --git a/crates/valence_protocol/build/block.rs b/crates/valence_block/build.rs similarity index 98% rename from crates/valence_protocol/build/block.rs rename to crates/valence_block/build.rs index 11987f5..ddae3b7 100644 --- a/crates/valence_protocol/build/block.rs +++ b/crates/valence_block/build.rs @@ -4,8 +4,7 @@ use heck::{ToPascalCase, ToShoutySnakeCase}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use serde::Deserialize; - -use crate::ident; +use valence_build_utils::{ident, rerun_if_changed, write_generated_file}; #[derive(Deserialize, Clone, Debug)] struct TopLevel { @@ -69,12 +68,18 @@ struct Shape { max_z: f64, } -pub fn build() -> anyhow::Result { +pub fn main() -> anyhow::Result<()> { + rerun_if_changed(["../../extracted/blocks.json"]); + + write_generated_file(build()?, "block.rs") +} + +fn build() -> anyhow::Result { let TopLevel { blocks, shapes, block_entity_types, - } = 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(); @@ -948,13 +953,13 @@ pub fn build() -> anyhow::Result { } impl Encode for BlockEntityKind { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { VarInt(self.id() as i32).encode(w) } } impl<'a> Decode<'a> for BlockEntityKind { - fn decode(r: &mut &'a [u8]) -> Result { + fn decode(r: &mut &'a [u8]) -> anyhow::Result { let id = VarInt::decode(r)?; Self::from_id(id.0 as u32).with_context(|| format!("id {}", id.0)) } diff --git a/crates/valence_protocol/src/block.rs b/crates/valence_block/src/lib.rs similarity index 75% rename from crates/valence_protocol/src/block.rs rename to crates/valence_block/src/lib.rs index ef644e9..a9fe07e 100644 --- a/crates/valence_protocol/src/block.rs +++ b/crates/valence_block/src/lib.rs @@ -1,4 +1,22 @@ +#![doc = include_str!("../README.md")] #![allow(clippy::all)] // TODO: block build script creates many warnings. +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] use std::fmt; use std::fmt::Display; @@ -6,11 +24,11 @@ use std::io::Write; use std::iter::FusedIterator; use anyhow::Context; - -use crate::ident::Ident; -use crate::item::ItemKind; -use crate::var_int::VarInt; -use crate::{ident, Decode, Encode, Result}; +use valence_core::ident; +use valence_core::ident::Ident; +use valence_core::item::ItemKind; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::{Decode, Encode}; include!(concat!(env!("OUT_DIR"), "/block.rs")); @@ -53,13 +71,13 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result { } impl Encode for BlockState { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { VarInt(self.to_raw() as i32).encode(w) } } impl Decode<'_> for BlockState { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { let id = VarInt::decode(r)?.0; let errmsg = "invalid block state ID"; @@ -68,13 +86,13 @@ impl Decode<'_> for BlockState { } impl Encode for BlockKind { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { VarInt(self.to_raw() as i32).encode(w) } } impl Decode<'_> for BlockKind { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { let id = VarInt::decode(r)?.0; let errmsg = "invalid block kind ID"; diff --git a/crates/valence_build_utils/Cargo.toml b/crates/valence_build_utils/Cargo.toml new file mode 100644 index 0000000..7bd2146 --- /dev/null +++ b/crates/valence_build_utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "valence_build_utils" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +syn.workspace = true +proc-macro2.workspace = true diff --git a/crates/valence_build_utils/README.md b/crates/valence_build_utils/README.md new file mode 100644 index 0000000..fa275e1 --- /dev/null +++ b/crates/valence_build_utils/README.md @@ -0,0 +1,3 @@ +# valence_build_utils + +Common code used in build scripts. diff --git a/crates/valence_build_utils/src/lib.rs b/crates/valence_build_utils/src/lib.rs new file mode 100644 index 0000000..97c2217 --- /dev/null +++ b/crates/valence_build_utils/src/lib.rs @@ -0,0 +1,59 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + +use std::path::Path; +use std::process::Command; +use std::{env, fs}; + +use anyhow::Context; +use proc_macro2::{Ident, Span, TokenStream}; + +pub fn write_generated_file(content: TokenStream, out_file: &str) -> anyhow::Result<()> { + let out_dir = env::var_os("OUT_DIR").context("failed to get OUT_DIR env var")?; + let path = Path::new(&out_dir).join(out_file); + let code = content.to_string(); + + fs::write(&path, code)?; + + // Try to format the output for debugging purposes. + // Doesn't matter if rustfmt is unavailable. + let _ = Command::new("rustfmt").arg(path).output(); + + Ok(()) +} + +pub fn ident(s: impl AsRef) -> Ident { + let s = s.as_ref().trim(); + + // Parse the ident from a str. If the string is a Rust keyword, stick an + // underscore in front. + syn::parse_str::(s) + .unwrap_or_else(|_| Ident::new(format!("_{s}").as_str(), Span::call_site())) +} + +pub fn rerun_if_changed(files: [&str; N]) { + for file in files { + assert!( + Path::new(file).exists(), + "File \"{file}\" does not exist. Did you forget to update the path?" + ); + + println!("cargo:rerun-if-changed={file}"); + } +} diff --git a/crates/valence_client/Cargo.toml b/crates/valence_client/Cargo.toml new file mode 100644 index 0000000..8394490 --- /dev/null +++ b/crates/valence_client/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "valence_client" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +bytes.workspace = true +glam.workspace = true +rand.workspace = true +tracing.workspace = true +uuid.workspace = true +valence_biome.workspace = true +valence_core.workspace = true +valence_dimension.workspace = true +valence_entity.workspace = true +valence_instance.workspace = true +valence_registry.workspace = true + diff --git a/crates/valence_client/README.md b/crates/valence_client/README.md new file mode 100644 index 0000000..4e21dfc --- /dev/null +++ b/crates/valence_client/README.md @@ -0,0 +1,5 @@ +# valence_client + +Manages core components and systems related to Minecraft clients. + +A client is a Minecraft player entity backed by an abstract network connection and other necessary components. See [`Client`] for more information. diff --git a/crates/valence/src/client/action.rs b/crates/valence_client/src/action.rs similarity index 88% rename from crates/valence/src/client/action.rs rename to crates/valence_client/src/action.rs index dc15f40..086d468 100644 --- a/crates/valence/src/client/action.rs +++ b/crates/valence_client/src/action.rs @@ -1,7 +1,7 @@ -use valence_protocol::block_pos::BlockPos; -use valence_protocol::packet::c2s::play::player_action::Action; -use valence_protocol::packet::c2s::play::PlayerActionC2s; -use valence_protocol::types::Direction; +use valence_core::block_pos::BlockPos; +use valence_core::direction::Direction; +use valence_core::packet::c2s::play::player_action::Action; +use valence_core::packet::c2s::play::PlayerActionC2s; use super::*; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; @@ -13,11 +13,7 @@ pub(super) fn build(app: &mut App) { .in_schedule(EventLoopSchedule) .in_base_set(EventLoopSet::PreUpdate), ) - .add_system( - acknowledge_player_actions - .in_base_set(CoreSet::PostUpdate) - .before(FlushPacketsSet), - ); + .add_system(acknowledge_player_actions.in_set(UpdateClientsSet)); } #[derive(Copy, Clone, Debug)] diff --git a/crates/valence/src/client/command.rs b/crates/valence_client/src/command.rs similarity index 95% rename from crates/valence/src/client/command.rs rename to crates/valence_client/src/command.rs index 100cb91..f6b9a91 100644 --- a/crates/valence/src/client/command.rs +++ b/crates/valence_client/src/command.rs @@ -1,10 +1,10 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use valence_protocol::packet::c2s::play::client_command::Action; -use valence_protocol::packet::c2s::play::ClientCommandC2s; +use valence_core::packet::c2s::play::client_command::Action; +use valence_core::packet::c2s::play::ClientCommandC2s; +use valence_entity::entity::Flags; +use valence_entity::{entity, Pose}; -use crate::entity::entity::Flags; -use crate::entity::{entity, Pose}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { diff --git a/crates/valence/src/event_loop.rs b/crates/valence_client/src/event_loop.rs similarity index 85% rename from crates/valence/src/event_loop.rs rename to crates/valence_client/src/event_loop.rs index fed9622..a6930fa 100644 --- a/crates/valence/src/event_loop.rs +++ b/crates/valence_client/src/event_loop.rs @@ -6,29 +6,29 @@ use bevy_ecs::schedule::ScheduleLabel; use bevy_ecs::system::SystemState; use bytes::Bytes; use tracing::{debug, warn}; -use valence_protocol::{Decode, Packet}; +use valence_core::packet::{Decode, Packet}; -use crate::client::Client; +use crate::{Client, SpawnClientsSet}; -pub(crate) struct EventLoopPlugin; +pub(super) fn build(app: &mut App) { + app.configure_set( + RunEventLoopSet + .in_base_set(CoreSet::PreUpdate) + .after(SpawnClientsSet), + ) + .add_system(run_event_loop.in_set(RunEventLoopSet)) + .add_event::(); -impl Plugin for EventLoopPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.configure_set(RunEventLoopSet.in_base_set(CoreSet::PreUpdate)) - .add_system(run_event_loop.in_set(RunEventLoopSet)) - .add_event::(); + // Add the event loop schedule. + let mut event_loop = Schedule::new(); + event_loop.set_default_base_set(EventLoopSet::Update); + event_loop.configure_sets(( + EventLoopSet::PreUpdate.before(EventLoopSet::Update), + EventLoopSet::Update.before(EventLoopSet::PostUpdate), + EventLoopSet::PostUpdate, + )); - // Add the event loop schedule. - let mut event_loop = Schedule::new(); - event_loop.set_default_base_set(EventLoopSet::Update); - event_loop.configure_sets(( - EventLoopSet::PreUpdate.before(EventLoopSet::Update), - EventLoopSet::Update.before(EventLoopSet::PostUpdate), - EventLoopSet::PostUpdate, - )); - - app.add_schedule(EventLoopSchedule, event_loop); - } + app.add_schedule(EventLoopSchedule, event_loop); } /// The [`ScheduleLabel`] for the event loop [`Schedule`]. @@ -97,7 +97,8 @@ impl PacketEvent { } /// An exclusive system for running the event loop schedule. -pub(crate) fn run_event_loop( +#[allow(clippy::type_complexity)] +fn run_event_loop( world: &mut World, state: &mut SystemState<( Query<(Entity, &mut Client)>, diff --git a/crates/valence/src/client/interact_entity.rs b/crates/valence_client/src/interact_entity.rs similarity index 88% rename from crates/valence/src/client/interact_entity.rs rename to crates/valence_client/src/interact_entity.rs index 38cc428..110563d 100644 --- a/crates/valence/src/client/interact_entity.rs +++ b/crates/valence_client/src/interact_entity.rs @@ -1,9 +1,9 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use valence_protocol::packet::c2s::play::player_interact_entity::EntityInteraction; -use valence_protocol::packet::c2s::play::PlayerInteractEntityC2s; +use valence_core::packet::c2s::play::player_interact_entity::EntityInteraction; +use valence_core::packet::c2s::play::PlayerInteractEntityC2s; +use valence_entity::EntityManager; -use crate::entity::EntityManager; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { diff --git a/crates/valence/src/client/keepalive.rs b/crates/valence_client/src/keepalive.rs similarity index 84% rename from crates/valence/src/client/keepalive.rs rename to crates/valence_client/src/keepalive.rs index 57d1ac8..baa5af5 100644 --- a/crates/valence/src/client/keepalive.rs +++ b/crates/valence_client/src/keepalive.rs @@ -1,19 +1,16 @@ -use valence_protocol::packet::c2s::play::KeepAliveC2s; +use valence_core::packet::c2s::play::KeepAliveC2s; +use valence_core::CoreSettings; use super::*; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { - app.add_system( - send_keepalive - .in_base_set(CoreSet::PostUpdate) - .before(FlushPacketsSet), - ) - .add_system( - handle_keepalive_response - .in_base_set(EventLoopSet::PreUpdate) - .in_schedule(EventLoopSchedule), - ); + app.add_system(send_keepalive.in_set(UpdateClientsSet)) + .add_system( + handle_keepalive_response + .in_base_set(EventLoopSet::PreUpdate) + .in_schedule(EventLoopSchedule), + ); } #[derive(Component, Debug)] @@ -36,9 +33,10 @@ impl KeepaliveState { fn send_keepalive( mut clients: Query<(Entity, &mut Client, &mut KeepaliveState)>, server: Res, + settings: Res, mut commands: Commands, ) { - if server.current_tick() % (server.tps() * 10) == 0 { + if server.current_tick() % (settings.tick_rate.get() * 10) as i64 == 0 { let mut rng = rand::thread_rng(); let now = Instant::now(); diff --git a/crates/valence/src/client.rs b/crates/valence_client/src/lib.rs similarity index 80% rename from crates/valence/src/client.rs rename to crates/valence_client/src/lib.rs index 9a4cc12..37c6eb6 100644 --- a/crates/valence/src/client.rs +++ b/crates/valence_client/src/lib.rs @@ -1,6 +1,26 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use std::borrow::Cow; +use std::fmt; use std::net::IpAddr; -use std::num::Wrapping; +use std::ops::Deref; use std::time::Instant; use bevy_app::prelude::*; @@ -11,15 +31,20 @@ use bytes::{Bytes, BytesMut}; use glam::{DVec3, Vec3}; use rand::Rng; use tracing::warn; -use valence_protocol::block_pos::BlockPos; -use valence_protocol::byte_angle::ByteAngle; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::ident::Ident; -use valence_protocol::item::ItemStack; -use valence_protocol::packet::s2c::play::game_state_change::GameEventKind; -use valence_protocol::packet::s2c::play::particle::Particle; -use valence_protocol::packet::s2c::play::player_position_look::Flags as PlayerPositionLookFlags; -use valence_protocol::packet::s2c::play::{ +use uuid::Uuid; +use valence_biome::BiomeRegistry; +use valence_core::block_pos::BlockPos; +use valence_core::chunk_pos::{ChunkPos, ChunkView}; +use valence_core::despawn::Despawned; +use valence_core::game_mode::GameMode; +use valence_core::ident::Ident; +use valence_core::packet::byte_angle::ByteAngle; +use valence_core::packet::encode::{PacketEncoder, WritePacket}; +use valence_core::packet::global_pos::GlobalPos; +use valence_core::packet::s2c::play::game_state_change::GameEventKind; +use valence_core::packet::s2c::play::particle::Particle; +use valence_core::packet::s2c::play::player_position_look::Flags as PlayerPositionLookFlags; +use valence_core::packet::s2c::play::{ ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, CustomPayloadS2c, DeathMessageS2c, DisconnectS2c, EntitiesDestroyS2c, EntitySetHeadYawS2c, EntitySpawnS2c, EntityStatusS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, ExperienceOrbSpawnS2c, GameJoinS2c, @@ -27,46 +52,52 @@ use valence_protocol::packet::s2c::play::{ PlayerActionResponseS2c, PlayerPositionLookS2c, PlayerRespawnS2c, PlayerSpawnPositionS2c, PlayerSpawnS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c, TitleS2c, UnloadChunkS2c, }; -use valence_protocol::sound::Sound; -use valence_protocol::text::Text; -use valence_protocol::types::{GlobalPos, SoundCategory}; -use valence_protocol::var_int::VarInt; -use valence_protocol::Packet; - -use crate::biome::BiomeRegistry; -use crate::component::{ - Despawned, GameMode, Location, Look, OldLocation, OldPosition, OnGround, Ping, Position, - Properties, ScratchBuf, UniqueId, Username, +use valence_core::packet::var_int::VarInt; +use valence_core::packet::Packet; +use valence_core::property::Property; +use valence_core::scratch::ScratchBuf; +use valence_core::sound::{Sound, SoundCategory}; +use valence_core::text::Text; +use valence_core::uuid::UniqueId; +use valence_core::Server; +use valence_entity::player::PlayerEntityBundle; +use valence_entity::{ + ClearEntityChangesSet, EntityId, EntityKind, EntityStatus, HeadYaw, Location, Look, ObjectData, + OldLocation, OldPosition, OnGround, PacketByteRange, Position, TrackedData, Velocity, }; -use crate::entity::player::PlayerEntityBundle; -use crate::entity::{ - EntityId, EntityKind, EntityStatus, HeadYaw, ObjectData, PacketByteRange, TrackedData, Velocity, -}; -use crate::instance::{Instance, WriteUpdatePacketsToInstancesSet}; -use crate::inventory::{Inventory, InventoryKind}; -use crate::packet::WritePacket; -use crate::registry_codec::{RegistryCodec, RegistryCodecSet}; -use crate::server::{NewClientInfo, Server}; -use crate::util::velocity_to_packet_units; -use crate::view::{ChunkPos, ChunkView}; +use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet}; +use valence_registry::{RegistryCodec, RegistryCodecSet}; pub mod action; pub mod command; +pub mod event_loop; pub mod interact_entity; pub mod keepalive; pub mod misc; pub mod movement; pub mod settings; pub mod teleport; +pub mod weather; -pub(crate) struct ClientPlugin; +pub struct ClientPlugin; /// When clients have their packet buffer flushed. Any system that writes -/// packets to clients should happen before this. Otherwise, the data +/// packets to clients should happen _before_ this. Otherwise, the data /// will arrive one tick late. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct FlushPacketsSet; +/// The [`SystemSet`] in [`CoreSet::PreUpdate`] where new clients should be +/// spawned. Systems that need to perform initialization work on clients before +/// users get access to it should run _after_ this set in +/// [`CoreSet::PreUpdate`]. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] + +pub struct SpawnClientsSet; + +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct UpdateClientsSet; + impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.add_systems( @@ -86,16 +117,20 @@ impl Plugin for ClientPlugin { init_tracked_data.after(WriteUpdatePacketsToInstancesSet), update_op_level, ) + .in_set(UpdateClientsSet), + ) + .configure_sets(( + SpawnClientsSet.in_base_set(CoreSet::PreUpdate), + UpdateClientsSet .in_base_set(CoreSet::PostUpdate) .before(FlushPacketsSet), - ) - .configure_set( - FlushPacketsSet - .in_base_set(CoreSet::PostUpdate) - .after(WriteUpdatePacketsToInstancesSet), - ) + ClearEntityChangesSet.after(UpdateClientsSet), + FlushPacketsSet.in_base_set(CoreSet::PostUpdate), + ClearInstanceChangesSet.after(FlushPacketsSet), + )) .add_system(flush_packets.in_set(FlushPacketsSet)); + event_loop::build(app); movement::build(app); command::build(app); keepalive::build(app); @@ -104,57 +139,54 @@ impl Plugin for ClientPlugin { misc::build(app); action::build(app); teleport::build(app); + weather::build(app); } } /// The bundle of components needed for clients to function. All components are /// required unless otherwise stated. #[derive(Bundle)] -pub(crate) struct ClientBundle { - client: Client, - settings: settings::ClientSettings, - scratch: ScratchBuf, - entity_remove_buf: EntityRemoveBuf, - username: Username, - ip: Ip, - properties: Properties, - compass_pos: CompassPos, - game_mode: GameMode, - op_level: OpLevel, - action_sequence: action::ActionSequence, - view_distance: ViewDistance, - old_view_distance: OldViewDistance, - death_location: DeathLocation, - keepalive_state: keepalive::KeepaliveState, - ping: Ping, - is_hardcore: IsHardcore, - prev_game_mode: PrevGameMode, - hashed_seed: HashedSeed, - reduced_debug_info: ReducedDebugInfo, - has_respawn_screen: HasRespawnScreen, - is_debug: IsDebug, - is_flat: IsFlat, - teleport_state: teleport::TeleportState, - cursor_item: CursorItem, - player_inventory_state: ClientInventoryState, - inventory: Inventory, - player: PlayerEntityBundle, +pub struct ClientBundle { + pub client: Client, + pub settings: settings::ClientSettings, + pub scratch: ScratchBuf, + pub entity_remove_buf: EntityRemoveBuf, + pub username: Username, + pub ip: Ip, + pub properties: Properties, + pub compass_pos: CompassPos, + pub game_mode: GameMode, + pub op_level: OpLevel, + pub action_sequence: action::ActionSequence, + pub view_distance: ViewDistance, + pub old_view_distance: OldViewDistance, + pub death_location: DeathLocation, + pub keepalive_state: keepalive::KeepaliveState, + pub ping: Ping, + pub is_hardcore: IsHardcore, + pub prev_game_mode: PrevGameMode, + pub hashed_seed: HashedSeed, + pub reduced_debug_info: ReducedDebugInfo, + pub has_respawn_screen: HasRespawnScreen, + pub is_debug: IsDebug, + pub is_flat: IsFlat, + pub teleport_state: teleport::TeleportState, + pub player: PlayerEntityBundle, } impl ClientBundle { - pub(crate) fn new( - info: NewClientInfo, - conn: Box, - enc: PacketEncoder, - ) -> Self { + pub fn new(args: ClientBundleArgs) -> Self { Self { - client: Client { conn, enc }, + client: Client { + conn: args.conn, + enc: args.enc, + }, settings: settings::ClientSettings::default(), scratch: ScratchBuf::default(), entity_remove_buf: EntityRemoveBuf(vec![]), - username: Username(info.username), - ip: Ip(info.ip), - properties: Properties(info.properties), + username: Username(args.username), + ip: Ip(args.ip), + properties: Properties(args.properties), compass_pos: CompassPos::default(), game_mode: GameMode::default(), op_level: OpLevel::default(), @@ -168,21 +200,30 @@ impl ClientBundle { is_hardcore: IsHardcore::default(), is_flat: IsFlat::default(), has_respawn_screen: HasRespawnScreen::default(), - cursor_item: CursorItem::default(), - player_inventory_state: ClientInventoryState::new(), - inventory: Inventory::new(InventoryKind::Player), prev_game_mode: PrevGameMode::default(), hashed_seed: HashedSeed::default(), reduced_debug_info: ReducedDebugInfo::default(), is_debug: IsDebug::default(), player: PlayerEntityBundle { - uuid: UniqueId(info.uuid), + uuid: UniqueId(args.uuid), ..Default::default() }, } } } +/// Arguments for [`ClientBundle::new`]. +pub struct ClientBundleArgs { + /// The username for the client. + pub username: String, + pub uuid: Uuid, + pub ip: IpAddr, + pub properties: Vec, + pub conn: Box, + /// The packet encoder to use. This should be in sync with [`Self::conn`]. + pub enc: PacketEncoder, +} + /// The main client component. Contains the underlying network connection and /// packet buffer. /// @@ -206,6 +247,7 @@ pub trait ClientConnection: Send + Sync + 'static { /// The number of pending packets waiting to be received via /// [`Self::try_recv`]. fn len(&self) -> usize; + fn is_empty(&self) -> bool { self.len() == 0 } @@ -380,8 +422,8 @@ impl Client { self.write_packet(&ParticleS2c { particle: Cow::Borrowed(particle), long_distance, - position: position.into().into(), - offset: offset.into().into(), + position: position.into(), + offset: offset.into(), max_speed, count, }) @@ -406,7 +448,7 @@ impl Client { self.write_packet(&PlaySoundS2c { id: sound.to_id(), category, - position: (position * 8.0).as_ivec3().into(), + position: (position * 8.0).as_ivec3(), volume, pitch, seed: rand::random(), @@ -417,7 +459,7 @@ impl Client { pub fn set_velocity(&mut self, velocity: impl Into) { self.write_packet(&EntityVelocityUpdateS2c { entity_id: VarInt(0), - velocity: velocity_to_packet_units(velocity.into()), + velocity: Velocity(velocity.into()).to_packet_units(), }); } @@ -476,6 +518,74 @@ impl EntityRemoveBuf { } } +#[derive(Component, Clone, PartialEq, Eq, Default, Debug)] +pub struct Username(pub String); + +impl Username { + pub fn is_valid(&self) -> bool { + is_valid_username(&self.0) + } +} + +impl fmt::Display for Username { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Returns whether or not the given string is a valid Minecraft username. +/// +/// A valid username is 3 to 16 characters long with only ASCII alphanumeric +/// characters. The username must match the regex `^[a-zA-Z0-9_]{3,16}$` to be +/// considered valid. +/// +/// # Examples +/// +/// ``` +/// # use valence_client::is_valid_username; +/// +/// assert!(is_valid_username("00a")); +/// assert!(is_valid_username("jeb_")); +/// +/// assert!(!is_valid_username("notavalidusername")); +/// assert!(!is_valid_username("NotValid!")); +/// ``` +pub fn is_valid_username(username: &str) -> bool { + (3..=16).contains(&username.len()) + && username + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_') +} + +#[derive(Component, Clone, PartialEq, Eq, Default, Debug)] +pub struct Properties(pub Vec); + +impl Properties { + /// Finds the property with the name "textures". + pub fn textures(&self) -> Option<&Property> { + self.0.iter().find(|prop| prop.name == "textures") + } + + /// Finds the property with the name "textures". + pub fn textures_mut(&mut self) -> Option<&mut Property> { + self.0.iter_mut().find(|prop| prop.name == "textures") + } +} + +impl From> for Properties { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl Deref for Properties { + type Target = [Property]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Component, Clone, PartialEq, Eq, Debug)] pub struct Ip(pub IpAddr); @@ -568,6 +678,16 @@ impl OldViewItem<'_> { #[derive(Component, Clone, PartialEq, Eq, Default, Debug)] pub struct DeathLocation(pub Option<(Ident, BlockPos)>); +/// Delay measured in milliseconds. Negative values indicate absence. +#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Ping(pub i32); + +impl Default for Ping { + fn default() -> Self { + Self(-1) + } +} + #[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct IsHardcore(pub bool); @@ -598,46 +718,6 @@ pub struct IsDebug(pub bool); #[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct IsFlat(pub bool); -/// The item stack that the client thinks it's holding under the mouse -/// cursor. -#[derive(Component, Clone, PartialEq, Default, Debug)] -pub struct CursorItem(pub Option); - -// TODO: move this component to inventory module? -/// Miscellaneous inventory data. -#[derive(Component, Debug)] -pub struct ClientInventoryState { - /// The current window ID. Incremented when inventories are opened. - pub(crate) window_id: u8, - pub(crate) state_id: Wrapping, - /// Tracks what slots have been changed by this client in this tick, so we - /// don't need to send updates for them. - pub(crate) slots_changed: u64, - /// Whether the client has updated the cursor item in this tick. This is not - /// on the `CursorItem` component to make maintaining accurate change - /// detection for end users easier. - pub(crate) client_updated_cursor_item: bool, - // TODO: make this a separate modifiable component. - pub(crate) held_item_slot: u16, -} - -impl ClientInventoryState { - fn new() -> Self { - Self { - window_id: 0, - state_id: Wrapping(0), - slots_changed: 0, - client_updated_cursor_item: false, - // First slot of the hotbar. - held_item_slot: 36, - } - } - - pub fn held_item_slot(&self) -> u16 { - self.held_item_slot - } -} - /// A system for adding [`Despawned`] components to disconnected clients. This /// works by listening for removed [`Client`] components. pub fn despawn_disconnected_clients( @@ -701,7 +781,7 @@ fn initial_join( _ = q.client.enc.prepend_packet(&GameJoinS2c { entity_id: 0, // We reserve ID 0 for clients. is_hardcore: q.is_hardcore.0, - game_mode: (*q.game_mode).into(), + game_mode: *q.game_mode, previous_game_mode: q.prev_game_mode.0.map(|g| g as i8).unwrap_or(-1), dimension_names, registry_codec: Cow::Borrowed(codec.cached_codec()), @@ -727,6 +807,7 @@ fn initial_join( } } +#[allow(clippy::type_complexity)] fn respawn( mut clients: Query< ( @@ -767,7 +848,7 @@ fn respawn( dimension_type_name: dimension_name.into(), dimension_name: dimension_name.into(), hashed_seed: hashed_seed.0, - game_mode: (*game_mode).into(), + game_mode: *game_mode, previous_game_mode: prev_game_mode.0.map(|g| g as i8).unwrap_or(-1), is_debug: is_debug.0, is_flat: is_flat.0, @@ -795,28 +876,28 @@ fn update_chunk_load_dist( } #[derive(WorldQuery)] -pub(crate) struct EntityInitQuery { - pub entity_id: &'static EntityId, - pub uuid: &'static UniqueId, - pub kind: &'static EntityKind, - pub look: &'static Look, - pub head_yaw: &'static HeadYaw, - pub on_ground: &'static OnGround, - pub object_data: &'static ObjectData, - pub velocity: &'static Velocity, - pub tracked_data: &'static TrackedData, +struct EntityInitQuery { + entity_id: &'static EntityId, + uuid: &'static UniqueId, + kind: &'static EntityKind, + look: &'static Look, + head_yaw: &'static HeadYaw, + on_ground: &'static OnGround, + object_data: &'static ObjectData, + velocity: &'static Velocity, + tracked_data: &'static TrackedData, } impl EntityInitQueryItem<'_> { /// Writes the appropriate packets to initialize an entity. This will spawn /// the entity and initialize tracked data. - pub fn write_init_packets(&self, pos: DVec3, mut writer: impl WritePacket) { + fn write_init_packets(&self, pos: DVec3, mut writer: impl WritePacket) { match *self.kind { EntityKind::MARKER => {} EntityKind::EXPERIENCE_ORB => { writer.write_packet(&ExperienceOrbSpawnS2c { entity_id: self.entity_id.get().into(), - position: pos.to_array(), + position: pos, count: self.object_data.0 as i16, }); } @@ -824,7 +905,7 @@ impl EntityInitQueryItem<'_> { writer.write_packet(&PlayerSpawnS2c { entity_id: self.entity_id.get().into(), player_uuid: self.uuid.0, - position: pos.to_array(), + position: pos, yaw: ByteAngle::from_degrees(self.look.yaw), pitch: ByteAngle::from_degrees(self.look.pitch), }); @@ -839,12 +920,12 @@ impl EntityInitQueryItem<'_> { entity_id: self.entity_id.get().into(), object_uuid: self.uuid.0, kind: self.kind.get().into(), - position: pos.to_array(), + position: pos, pitch: ByteAngle::from_degrees(self.look.pitch), yaw: ByteAngle::from_degrees(self.look.yaw), head_yaw: ByteAngle::from_degrees(self.head_yaw.0), data: self.object_data.0.into(), - velocity: velocity_to_packet_units(self.velocity.0), + velocity: self.velocity.to_packet_units(), }), } @@ -857,6 +938,7 @@ impl EntityInitQueryItem<'_> { } } +#[allow(clippy::type_complexity)] fn read_data_in_old_view( mut clients: Query<( &mut Client, @@ -892,10 +974,7 @@ fn read_data_in_old_view( if let Some(cell) = instance.partition.get(&pos) { if cell.chunk_removed && cell.chunk.is_none() { // Chunk was previously loaded and is now deleted. - client.write_packet(&UnloadChunkS2c { - chunk_x: pos.x, - chunk_z: pos.z, - }); + client.write_packet(&UnloadChunkS2c { pos }); } if let Some(chunk) = &cell.chunk { @@ -952,6 +1031,7 @@ fn read_data_in_old_view( /// /// This handles the situation when a client changes instances or chunk /// position. It must run after [`read_data_in_old_view`]. +#[allow(clippy::type_complexity)] fn update_view( mut clients: Query< ( @@ -1009,10 +1089,7 @@ fn update_view( if let Some(cell) = old_instance.partition.get(&pos) { // Unload the chunk at this cell if it was loaded. if cell.chunk.is_some() { - client.write_packet(&UnloadChunkS2c { - chunk_x: pos.x, - chunk_z: pos.z, - }); + client.write_packet(&UnloadChunkS2c { pos }); } // Unload all the entities in the cell. @@ -1069,10 +1146,7 @@ fn update_view( if let Some(cell) = instance.partition.get(&pos) { // Unload the chunk at this cell if it was loaded. if cell.chunk.is_some() { - client.write_packet(&UnloadChunkS2c { - chunk_x: pos.x, - chunk_z: pos.z, - }); + client.write_packet(&UnloadChunkS2c { pos }); } // Unload all the entities in the cell. @@ -1204,102 +1278,3 @@ fn update_op_level(mut clients: Query<(&mut Client, &OpLevel), Changed> }); } } - -#[cfg(test)] -mod tests { - use std::collections::BTreeSet; - - use bevy_app::App; - use bevy_ecs::world::EntityMut; - use valence_protocol::packet::s2c::play::ChunkDataS2c; - use valence_protocol::packet::S2cPlayPacket; - - use super::*; - use crate::instance::Chunk; - use crate::unit_test::util::scenario_single_client; - - #[test] - fn client_chunk_view_change() { - let mut app = App::new(); - - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - let mut instance = app - .world - .query::<&mut Instance>() - .single_mut(&mut app.world); - - for z in -15..15 { - for x in -15..15 { - instance.insert_chunk([x, z], Chunk::default()); - } - } - - let mut client = app.world.entity_mut(client_ent); - - client.get_mut::().unwrap().set([8.0, 0.0, 8.0]); - client.get_mut::().unwrap().0 = 6; - - // Tick - app.update(); - let mut client = app.world.entity_mut(client_ent); - - let mut loaded_chunks = BTreeSet::new(); - - for pkt in client_helper.collect_sent() { - if let S2cPlayPacket::ChunkDataS2c(ChunkDataS2c { - chunk_x, chunk_z, .. - }) = pkt - { - assert!( - loaded_chunks.insert(ChunkPos::new(chunk_x, chunk_z)), - "({chunk_x}, {chunk_z})" - ); - } - } - - for pos in view(&client).iter() { - assert!(loaded_chunks.contains(&pos), "{pos:?}"); - } - - assert!(!loaded_chunks.is_empty()); - - // Move the client to the adjacent chunk. - client.get_mut::().unwrap().set([24.0, 0.0, 24.0]); - - // Tick - app.update(); - let client = app.world.entity_mut(client_ent); - - for pkt in client_helper.collect_sent() { - match pkt { - S2cPlayPacket::ChunkDataS2c(ChunkDataS2c { - chunk_x, chunk_z, .. - }) => { - assert!( - loaded_chunks.insert(ChunkPos::new(chunk_x, chunk_z)), - "({chunk_x}, {chunk_z})" - ); - } - S2cPlayPacket::UnloadChunkS2c(UnloadChunkS2c { chunk_x, chunk_z }) => { - assert!( - loaded_chunks.remove(&ChunkPos::new(chunk_x, chunk_z)), - "({chunk_x}, {chunk_z})" - ); - } - _ => {} - } - } - - for pos in view(&client).iter() { - assert!(loaded_chunks.contains(&pos), "{pos:?}"); - } - } - - fn view(client: &EntityMut) -> ChunkView { - let chunk_pos = client.get::().unwrap().chunk_pos(); - let view_dist = client.get::().unwrap().0; - - ChunkView::new(chunk_pos, view_dist) - } -} diff --git a/crates/valence/src/client/misc.rs b/crates/valence_client/src/misc.rs similarity index 95% rename from crates/valence/src/client/misc.rs rename to crates/valence_client/src/misc.rs index 39555ff..a3f6fb0 100644 --- a/crates/valence/src/client/misc.rs +++ b/crates/valence_client/src/misc.rs @@ -1,15 +1,16 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use glam::Vec3; -use valence_protocol::block_pos::BlockPos; -use valence_protocol::packet::c2s::play::{ +use valence_core::block_pos::BlockPos; +use valence_core::direction::Direction; +use valence_core::hand::Hand; +use valence_core::packet::c2s::play::{ ChatMessageC2s, ClientStatusC2s, HandSwingC2s, PlayerInteractBlockC2s, PlayerInteractItemC2s, ResourcePackStatusC2s, }; -use valence_protocol::types::{Direction, Hand}; +use valence_entity::{EntityAnimation, EntityAnimations}; use super::action::ActionSequence; -use crate::entity::{EntityAnimation, EntityAnimations}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { @@ -118,7 +119,7 @@ fn handle_misc_packets( hand: pkt.hand, position: pkt.position, face: pkt.face, - cursor_pos: pkt.cursor_pos.into(), + cursor_pos: pkt.cursor_pos, head_inside_block: pkt.head_inside_block, sequence: pkt.sequence.0, }); diff --git a/crates/valence/src/client/movement.rs b/crates/valence_client/src/movement.rs similarity index 95% rename from crates/valence/src/client/movement.rs rename to crates/valence_client/src/movement.rs index 16e01c3..941a8cc 100644 --- a/crates/valence/src/client/movement.rs +++ b/crates/valence_client/src/movement.rs @@ -1,13 +1,12 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use glam::DVec3; -use valence_protocol::packet::c2s::play::{ +use valence_core::packet::c2s::play::{ Full, LookAndOnGround, OnGroundOnly, PositionAndOnGround, VehicleMoveC2s, }; +use valence_entity::{HeadYaw, Look, OnGround, Position}; use super::teleport::TeleportState; -use crate::component::{Look, OnGround, Position}; -use crate::entity::HeadYaw; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { @@ -56,7 +55,7 @@ fn handle_client_movement( { let mov = Movement { client: packet.client, - position: pkt.position.into(), + position: pkt.position, old_position: pos.0, look: *look, old_look: *look, @@ -80,7 +79,7 @@ fn handle_client_movement( { let mov = Movement { client: packet.client, - position: pkt.position.into(), + position: pkt.position, old_position: pos.0, look: Look { yaw: pkt.yaw, @@ -158,7 +157,7 @@ fn handle_client_movement( { let mov = Movement { client: packet.client, - position: pkt.position.into(), + position: pkt.position, old_position: pos.0, look: Look { yaw: pkt.yaw, diff --git a/crates/valence/src/client/settings.rs b/crates/valence_client/src/settings.rs similarity index 85% rename from crates/valence/src/client/settings.rs rename to crates/valence_client/src/settings.rs index e780b6f..3e6581f 100644 --- a/crates/valence/src/client/settings.rs +++ b/crates/valence_client/src/settings.rs @@ -1,9 +1,8 @@ -pub use valence_protocol::packet::c2s::play::client_settings::ChatMode; -// use valence_protocol::packet::c2s::play::client_settings::MainArm; -use valence_protocol::packet::c2s::play::ClientSettingsC2s; +pub use valence_core::packet::c2s::play::client_settings::ChatMode; +use valence_core::packet::c2s::play::ClientSettingsC2s; +pub use valence_entity::player::{MainArm, PlayerModelParts}; use super::*; -pub use crate::entity::player::{MainArm, PlayerModelParts}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { diff --git a/crates/valence/src/client/teleport.rs b/crates/valence_client/src/teleport.rs similarity index 89% rename from crates/valence/src/client/teleport.rs rename to crates/valence_client/src/teleport.rs index c1bd436..003ab6e 100644 --- a/crates/valence/src/client/teleport.rs +++ b/crates/valence_client/src/teleport.rs @@ -1,20 +1,15 @@ -use valence_protocol::packet::c2s::play::TeleportConfirmC2s; +use valence_core::packet::c2s::play::TeleportConfirmC2s; use super::*; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; pub(super) fn build(app: &mut App) { - app.add_system( - teleport - .after(update_view) - .before(FlushPacketsSet) - .in_base_set(CoreSet::PostUpdate), - ) - .add_system( - handle_teleport_confirmations - .in_schedule(EventLoopSchedule) - .in_base_set(EventLoopSet::PreUpdate), - ); + app.add_system(teleport.after(update_view).in_set(UpdateClientsSet)) + .add_system( + handle_teleport_confirmations + .in_schedule(EventLoopSchedule) + .in_base_set(EventLoopSet::PreUpdate), + ); } #[derive(Component, Debug)] @@ -56,6 +51,7 @@ impl TeleportState { /// /// This should happen after chunks are loaded so the client doesn't fall though /// the floor. +#[allow(clippy::type_complexity)] fn teleport( mut clients: Query< (&mut Client, &mut TeleportState, &Position, &Look), @@ -79,7 +75,7 @@ fn teleport( .with_x_rot(!changed_pitch); client.write_packet(&PlayerPositionLookS2c { - position: if changed_pos { pos.0.into() } else { [0.0; 3] }, + position: if changed_pos { pos.0 } else { DVec3::ZERO }, yaw: if changed_yaw { look.yaw } else { 0.0 }, pitch: if changed_pitch { look.pitch } else { 0.0 }, flags, diff --git a/crates/valence_client/src/weather.rs b/crates/valence_client/src/weather.rs new file mode 100644 index 0000000..c47d2ed --- /dev/null +++ b/crates/valence_client/src/weather.rs @@ -0,0 +1,226 @@ +//! The weather system. +//! +//! This module contains the systems and components needed to handle +//! weather. +//! +//! # Components +//! +//! The components may be attached to clients or instances. +//! +//! - [`Rain`]: When attached, raining begin and rain level set events are +//! emitted. When removed, the end raining event is emitted. +//! - [`Thunder`]: When attached, thunder level set event is emitted. When +//! removed, the thunder level set to zero event is emitted. +//! +//! New joined players are handled, so that they are get weather events from +//! the instance. + +use valence_core::packet::s2c::play::game_state_change::GameEventKind; +use valence_core::packet::s2c::play::GameStateChangeS2c; + +use super::*; + +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct UpdateWeatherPerInstanceSet; + +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct UpdateWeatherPerClientSet; + +pub(super) fn build(app: &mut App) { + app.configure_set( + UpdateWeatherPerInstanceSet + .in_base_set(CoreSet::PostUpdate) + .before(WriteUpdatePacketsToInstancesSet), + ) + .configure_set( + UpdateWeatherPerClientSet + .in_base_set(CoreSet::PostUpdate) + .before(FlushPacketsSet), + ) + .add_systems( + ( + handle_rain_begin_per_instance, + handle_rain_change_per_instance, + handle_rain_end_per_instance, + handle_thunder_change_per_instance, + handle_thunder_end_per_instance, + ) + .chain() + .in_set(UpdateWeatherPerInstanceSet) + .before(UpdateWeatherPerClientSet), + ) + .add_systems( + ( + handle_rain_begin_per_client, + handle_rain_change_per_client, + handle_rain_end_per_client, + handle_thunder_change_per_client, + handle_thunder_end_per_client, + ) + .chain() + .in_set(UpdateWeatherPerClientSet), + ) + .add_system( + handle_weather_for_joined_player + .before(UpdateWeatherPerClientSet) + .in_base_set(CoreSet::PostUpdate), + ); +} + +/// Contains the rain level. +/// +/// Valid values are within `0.0..=1.0`. +#[derive(Component)] +pub struct Rain(pub f32); + +/// Contains the thunder level. +/// +/// Valid values are within `0.0..=1.0`. +#[derive(Component)] +pub struct Thunder(pub f32); + +fn handle_weather_for_joined_player( + mut clients: Query<(&mut Client, &Location), Added>, + weathers: Query<(Option<&Rain>, Option<&Thunder>), With>, +) { + for (mut client, loc) in &mut clients { + if let Ok((rain, thunder)) = weathers.get(loc.0) { + if let Some(level) = rain { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::BeginRaining, + value: 0.0, + }); + + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::RainLevelChange, + value: level.0, + }); + } + + if let Some(level) = thunder { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: level.0, + }); + } + } + } +} + +fn handle_rain_begin_per_instance(mut instances: Query<&mut Instance, Added>) { + for mut instance in &mut instances { + instance.write_packet(&GameStateChangeS2c { + kind: GameEventKind::BeginRaining, + value: f32::default(), + }); + } +} + +fn handle_rain_change_per_instance(mut instances: Query<(&mut Instance, &Rain), Changed>) { + for (mut instance, rain) in &mut instances { + instance.write_packet(&GameStateChangeS2c { + kind: GameEventKind::RainLevelChange, + value: rain.0, + }); + } +} + +fn handle_rain_end_per_instance( + mut instances: Query<&mut Instance>, + mut removed: RemovedComponents, +) { + for entity in &mut removed { + if let Ok(mut instance) = instances.get_mut(entity) { + instance.write_packet(&GameStateChangeS2c { + kind: GameEventKind::EndRaining, + value: 0.0, + }); + } + } +} + +fn handle_thunder_change_per_instance( + mut instances: Query<(&mut Instance, &Thunder), Changed>, +) { + for (mut instance, thunder) in &mut instances { + instance.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: thunder.0, + }); + } +} + +fn handle_thunder_end_per_instance( + mut instances: Query<&mut Instance>, + mut removed: RemovedComponents, +) { + for entity in &mut removed { + if let Ok(mut instance) = instances.get_mut(entity) { + instance.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: 0.0, + }); + } + } +} + +fn handle_rain_begin_per_client(mut clients: Query<&mut Client, (Added, Without)>) { + for mut client in &mut clients { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::BeginRaining, + value: 0.0, + }); + } +} + +#[allow(clippy::type_complexity)] +fn handle_rain_change_per_client( + mut clients: Query<(&mut Client, &Rain), (Changed, Without)>, +) { + for (mut client, rain) in &mut clients { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::RainLevelChange, + value: rain.0, + }); + } +} + +fn handle_rain_end_per_client( + mut clients: Query<&mut Client>, + mut removed: RemovedComponents, +) { + for entity in &mut removed { + if let Ok(mut client) = clients.get_mut(entity) { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::EndRaining, + value: f32::default(), + }); + } + } +} + +#[allow(clippy::type_complexity)] +fn handle_thunder_change_per_client( + mut clients: Query<(&mut Client, &Thunder), (Changed, Without)>, +) { + for (mut client, thunder) in &mut clients { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: thunder.0, + }); + } +} + +fn handle_thunder_end_per_client( + mut clients: Query<&mut Client, Without>, + mut removed: RemovedComponents, +) { + for entity in &mut removed { + if let Ok(mut client) = clients.get_mut(entity) { + client.write_packet(&GameStateChangeS2c { + kind: GameEventKind::ThunderLevelChange, + value: 0.0, + }); + } + } +} diff --git a/crates/valence_core/Cargo.toml b/crates/valence_core/Cargo.toml new file mode 100644 index 0000000..74a8baa --- /dev/null +++ b/crates/valence_core/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "valence_core" +version.workspace = true +edition.workspace = true +build = "build/main.rs" + +[features] +encryption = ["dep:aes", "dep:cfb8"] +compression = ["dep:flate2"] + +[dependencies] +aes = { workspace = true, optional = true } +anyhow.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +bitfield-struct.workspace = true +byteorder.workspace = true +bytes.workspace = true +cfb8 = { workspace = true, optional = true } +flate2 = { workspace = true, optional = true } +glam.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +thiserror.workspace = true +tracing.workspace = true +uuid = { workspace = true, features = ["serde"] } +valence_nbt.workspace = true +valence_core_macros.workspace = true +url.workspace = true +base64.workspace = true +rand.workspace = true + +[dev-dependencies] +rand.workspace = true +valence_core = { workspace = true, features = ["compression"] } + +[build-dependencies] +anyhow.workspace = true +heck.workspace = true +proc-macro2.workspace = true +quote.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +valence_build_utils.workspace = true diff --git a/crates/valence_core/README.md b/crates/valence_core/README.md new file mode 100644 index 0000000..9a5df31 --- /dev/null +++ b/crates/valence_core/README.md @@ -0,0 +1,3 @@ +# valence_core + +Contains foundational code or modules too small to deserve their own crate. The contents of `valence_core` are re-exported by the main `valence` crate. \ No newline at end of file diff --git a/crates/valence_protocol/build/item.rs b/crates/valence_core/build/item.rs similarity index 99% rename from crates/valence_protocol/build/item.rs rename to crates/valence_core/build/item.rs index 22c0a55..84315f3 100644 --- a/crates/valence_protocol/build/item.rs +++ b/crates/valence_core/build/item.rs @@ -3,8 +3,7 @@ use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; - -use crate::ident; +use valence_build_utils::ident; #[derive(Deserialize, Clone, Debug)] struct Item { @@ -280,6 +279,7 @@ pub fn build() -> anyhow::Result { } } + /* /// Constructs an item kind from a block kind. /// /// [`ItemKind::Air`] is used to indicate the absence of an item. @@ -292,7 +292,7 @@ pub fn build() -> anyhow::Result { /// If the given item kind doesn't have a corresponding block kind, `None` is returned. pub const fn to_block_kind(self) -> Option { BlockKind::from_item_kind(self) - } + }*/ /// An array of all item kinds. pub const ALL: [Self; #item_kind_count] = [#(Self::#item_kind_variants,)*]; diff --git a/crates/valence_core/build/main.rs b/crates/valence_core/build/main.rs new file mode 100644 index 0000000..4291d12 --- /dev/null +++ b/crates/valence_core/build/main.rs @@ -0,0 +1,22 @@ +use valence_build_utils::{rerun_if_changed, write_generated_file}; + +mod item; +mod packet_id; +mod sound; +mod translation_key; + +pub fn main() -> anyhow::Result<()> { + rerun_if_changed([ + "../../extracted/items.json", + "../../extracted/packets.json", + "../../extracted/sounds.json", + "../../extracted/translation_keys.json", + ]); + + write_generated_file(item::build()?, "item.rs")?; + write_generated_file(sound::build()?, "sound.rs")?; + write_generated_file(translation_key::build()?, "translation_key.rs")?; + write_generated_file(packet_id::build()?, "packet_id.rs")?; + + Ok(()) +} diff --git a/crates/valence_protocol/build/packet_id.rs b/crates/valence_core/build/packet_id.rs similarity index 90% rename from crates/valence_protocol/build/packet_id.rs rename to crates/valence_core/build/packet_id.rs index 02ead8e..31168c8 100644 --- a/crates/valence_protocol/build/packet_id.rs +++ b/crates/valence_core/build/packet_id.rs @@ -2,8 +2,7 @@ use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; - -use crate::ident; +use valence_build_utils::ident; #[derive(Deserialize)] struct Packet { @@ -30,7 +29,7 @@ pub fn build() -> anyhow::Result { consts.extend([quote! { #[doc = #doc] #[allow(non_upper_case_globals)] - pub const #name_ident: i32 = #id; + pub(crate) const #name_ident: i32 = #id; }]); } diff --git a/crates/valence_protocol/build/sound.rs b/crates/valence_core/build/sound.rs similarity index 99% rename from crates/valence_protocol/build/sound.rs rename to crates/valence_core/build/sound.rs index 9b3308d..ac30789 100644 --- a/crates/valence_protocol/build/sound.rs +++ b/crates/valence_core/build/sound.rs @@ -2,8 +2,7 @@ use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; - -use crate::ident; +use valence_build_utils::ident; #[derive(Deserialize, Debug)] pub struct Sound { diff --git a/crates/valence_protocol/build/translation_key.rs b/crates/valence_core/build/translation_key.rs similarity index 91% rename from crates/valence_protocol/build/translation_key.rs rename to crates/valence_core/build/translation_key.rs index 7b80144..ee7c999 100644 --- a/crates/valence_protocol/build/translation_key.rs +++ b/crates/valence_core/build/translation_key.rs @@ -3,8 +3,7 @@ use heck::ToShoutySnakeCase; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; - -use crate::ident; +use valence_build_utils::ident; #[derive(Deserialize, Clone, Debug)] struct Translation { @@ -28,7 +27,7 @@ pub fn build() -> anyhow::Result { let const_id = ident(translation.key.to_shouty_snake_case()); let key = &translation.key; let english_translation = &translation.english_translation; - let doc = escape(english_translation); + let doc = format!("\"{}\"", escape(english_translation)); quote! { #[doc = #doc] diff --git a/crates/valence_core/src/aabb.rs b/crates/valence_core/src/aabb.rs new file mode 100644 index 0000000..80413d2 --- /dev/null +++ b/crates/valence_core/src/aabb.rs @@ -0,0 +1,38 @@ +use glam::DVec3; + +/// An axis-aligned bounding box. `min` is expected to be <= `max` +/// componentwise. +#[derive(Copy, Clone, PartialEq, Default, Debug)] +pub struct Aabb { + pub min: DVec3, + pub max: DVec3, +} + +impl Aabb { + pub fn new(p0: impl Into, p1: impl Into) -> Self { + let p0 = p0.into(); + let p1 = p1.into(); + Self { + min: p0.min(p1), + max: p0.max(p1), + } + } + + pub fn from_bottom_size(bottom: impl Into, size: impl Into) -> Self { + let bottom = bottom.into(); + let size = size.into(); + + Self { + min: DVec3 { + x: bottom.x - size.x / 2.0, + y: bottom.y, + z: bottom.z - size.z / 2.0, + }, + max: DVec3 { + x: bottom.x + size.x / 2.0, + y: bottom.y + size.y, + z: bottom.z + size.z / 2.0, + }, + } + } +} diff --git a/crates/valence_protocol/src/block_pos.rs b/crates/valence_core/src/block_pos.rs similarity index 95% rename from crates/valence_protocol/src/block_pos.rs rename to crates/valence_core/src/block_pos.rs index 1d28460..01fc236 100644 --- a/crates/valence_protocol/src/block_pos.rs +++ b/crates/valence_core/src/block_pos.rs @@ -2,8 +2,8 @@ use std::io::Write; use anyhow::bail; -use crate::types::Direction; -use crate::{Decode, Encode}; +use crate::direction::Direction; +use crate::packet::{Decode, Encode}; /// Represents an absolute block position in world space. #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] @@ -28,8 +28,8 @@ impl BlockPos { /// direction. /// /// ``` - /// use valence_protocol::block_pos::BlockPos; - /// use valence_protocol::types::Direction; + /// use valence_core::block_pos::BlockPos; + /// use valence_core::direction::Direction; /// /// let pos = BlockPos::new(0, 0, 0); /// let adj = pos.get_in_direction(Direction::South); diff --git a/crates/valence/src/view.rs b/crates/valence_core/src/chunk_pos.rs similarity index 94% rename from crates/valence/src/view.rs rename to crates/valence_core/src/chunk_pos.rs index 9290a99..d4a4994 100644 --- a/crates/valence/src/view.rs +++ b/crates/valence_core/src/chunk_pos.rs @@ -1,9 +1,10 @@ use glam::DVec3; -use valence_protocol::block_pos::BlockPos; -/// The X and Z position of a chunk in an -/// [`Instance`](crate::instance::Instance). -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug)] +use crate::block_pos::BlockPos; +use crate::packet::{Decode, Encode}; + +/// The X and Z position of a chunk. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug, Encode, Decode)] pub struct ChunkPos { /// The X position of the chunk. pub x: i32, @@ -120,7 +121,7 @@ impl ChunkView { // The foreach-based methods are optimizing better than the iterator ones. #[inline] - pub(crate) fn for_each(self, mut f: impl FnMut(ChunkPos)) { + pub fn for_each(self, mut f: impl FnMut(ChunkPos)) { let true_dist = self.dist as i32 + EXTRA_VIEW_RADIUS; for z in self.pos.z - true_dist..=self.pos.z + true_dist { @@ -134,7 +135,7 @@ impl ChunkView { } #[inline] - pub(crate) fn diff_for_each(self, other: Self, mut f: impl FnMut(ChunkPos)) { + pub fn diff_for_each(self, other: Self, mut f: impl FnMut(ChunkPos)) { self.for_each(|p| { if !other.contains(p) { f(p); diff --git a/crates/valence_core/src/despawn.rs b/crates/valence_core/src/despawn.rs new file mode 100644 index 0000000..a3bb97c --- /dev/null +++ b/crates/valence_core/src/despawn.rs @@ -0,0 +1,26 @@ +use bevy_ecs::prelude::*; + +/// A marker [`Component`] for entities that should be despawned at the end of +/// the tick. +/// +/// In Valence, some entities such as Minecraft entities must not be removed +/// from the [`World`] directly. Valence needs an opportunity to perform +/// deinitialization work while the entity's components still exist. +/// +/// To resolve this problem, you must give the entities you wish to despawn the +/// `Despawned` component. At the end of the tick, Valence will despawn all +/// entities with this component for you. +/// +/// The `Despawned` component can be used on entities that Valence does not know +/// about. The entity will be despawned regardless. +#[derive(Component, Copy, Clone, Default, PartialEq, Eq, Debug)] +pub struct Despawned; + +pub(super) fn despawn_marked_entities( + mut commands: Commands, + entities: Query>, +) { + for entity in &entities { + commands.entity(entity).despawn(); + } +} diff --git a/crates/valence_core/src/difficulty.rs b/crates/valence_core/src/difficulty.rs new file mode 100644 index 0000000..4283027 --- /dev/null +++ b/crates/valence_core/src/difficulty.rs @@ -0,0 +1,9 @@ +use crate::packet::{Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub enum Difficulty { + Peaceful, + Easy, + Normal, + Hard, +} diff --git a/crates/valence_core/src/direction.rs b/crates/valence_core/src/direction.rs new file mode 100644 index 0000000..a8f64fd --- /dev/null +++ b/crates/valence_core/src/direction.rs @@ -0,0 +1,19 @@ +use bevy_ecs::prelude::*; + +use crate::packet::{Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Component)] +pub enum Direction { + /// -Y + Down, + /// +Y + Up, + /// -Z + North, + /// +Z + South, + /// -X + West, + /// +X + East, +} diff --git a/crates/valence_core/src/game_mode.rs b/crates/valence_core/src/game_mode.rs new file mode 100644 index 0000000..715640b --- /dev/null +++ b/crates/valence_core/src/game_mode.rs @@ -0,0 +1,12 @@ +use bevy_ecs::prelude::*; + +use crate::packet::{Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode, Component)] +pub enum GameMode { + #[default] + Survival, + Creative, + Adventure, + Spectator, +} diff --git a/crates/valence_core/src/hand.rs b/crates/valence_core/src/hand.rs new file mode 100644 index 0000000..dc20640 --- /dev/null +++ b/crates/valence_core/src/hand.rs @@ -0,0 +1,8 @@ +use crate::packet::{Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Encode, Decode)] +pub enum Hand { + #[default] + Main, + Off, +} diff --git a/crates/valence_protocol/src/ident.rs b/crates/valence_core/src/ident.rs similarity index 91% rename from crates/valence_protocol/src/ident.rs rename to crates/valence_core/src/ident.rs index b44e8ed..66df6a4 100644 --- a/crates/valence_protocol/src/ident.rs +++ b/crates/valence_core/src/ident.rs @@ -11,7 +11,12 @@ use serde::de::Error as _; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; -use crate::{nbt, Decode, Encode}; +use crate::packet::{Decode, Encode}; + +#[doc(hidden)] +pub mod __private { + pub use valence_core_macros::parse_ident_str; +} /// A wrapper around a string type `S` which guarantees the wrapped string is a /// valid resource identifier. @@ -29,6 +34,28 @@ pub struct Ident { string: S, } +/// Creates a new [`Ident`] at compile time from a string literal. A compile +/// error is raised if the string is not a valid resource identifier. +/// +/// The type of the expression returned by this macro is `Ident<&'static str>`. +/// +/// # Examples +/// +/// ``` +/// # use valence_core::{ident, ident::Ident}; +/// let my_ident: Ident<&'static str> = ident!("apple"); +/// +/// println!("{my_ident}"); +/// ``` +#[macro_export] +macro_rules! ident { + ($string:literal) => { + $crate::ident::Ident::<&'static str>::new_unchecked( + $crate::ident::__private::parse_ident_str!($string), + ) + }; +} + /// The error type created when an [`Ident`] cannot be parsed from a /// string. Contains the string that failed to parse. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)] @@ -307,9 +334,9 @@ where } } -impl From> for nbt::Value +impl From> for valence_nbt::Value where - S: Into, + S: Into, { fn from(value: Ident) -> Self { value.into_inner().into() @@ -341,7 +368,6 @@ where #[cfg(test)] mod tests { use super::*; - use crate::ident; #[test] fn check_namespace_and_path() { diff --git a/crates/valence_protocol/src/item.rs b/crates/valence_core/src/item.rs similarity index 80% rename from crates/valence_protocol/src/item.rs rename to crates/valence_core/src/item.rs index 95f9bb6..e975454 100644 --- a/crates/valence_protocol/src/item.rs +++ b/crates/valence_core/src/item.rs @@ -3,9 +3,8 @@ 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}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; include!(concat!(env!("OUT_DIR"), "/item.rs")); @@ -16,15 +15,15 @@ pub struct ItemStack { pub nbt: Option, } -pub const STACK_MIN: u8 = 1; -pub const STACK_MAX: u8 = 127; - impl ItemStack { + pub const STACK_MIN: u8 = 1; + pub const STACK_MAX: u8 = 127; + #[must_use] pub fn new(item: ItemKind, count: u8, nbt: Option) -> Self { Self { item, - count: count.clamp(STACK_MIN, STACK_MAX), + count: count.clamp(Self::STACK_MIN, Self::STACK_MAX), nbt, } } @@ -55,7 +54,7 @@ impl ItemStack { /// 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); + self.count = count.clamp(Self::STACK_MIN, Self::STACK_MAX); } } @@ -66,13 +65,13 @@ impl Default for ItemStack { } impl Encode for Option { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { self.as_ref().encode(w) } } impl<'a> Encode for Option<&'a ItemStack> { - fn encode(&self, mut w: impl Write) -> Result<()> { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { match *self { None => false.encode(w), Some(s) => { @@ -89,7 +88,7 @@ impl<'a> Encode for Option<&'a ItemStack> { } impl Decode<'_> for Option { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { let present = bool::decode(r)?; if !present { return Ok(None); @@ -99,8 +98,10 @@ impl Decode<'_> for Option { let count = u8::decode(r)?; ensure!( - (STACK_MIN..=STACK_MAX).contains(&count), - "invalid item stack count (got {count}, expected {STACK_MIN}..={STACK_MAX})" + (ItemStack::STACK_MIN..=ItemStack::STACK_MAX).contains(&count), + "invalid item stack count (got {count}, expected {}..={})", + ItemStack::STACK_MIN, + ItemStack::STACK_MAX, ); let nbt = if let [0, rest @ ..] = *r { @@ -115,13 +116,13 @@ impl Decode<'_> for Option { } impl Encode for ItemKind { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { VarInt(self.to_raw() as i32).encode(w) } } impl Decode<'_> for ItemKind { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { let id = VarInt::decode(r)?.0; let errmsg = "invalid item ID"; @@ -129,9 +130,11 @@ impl Decode<'_> for ItemKind { } } +/* #[cfg(test)] mod tests { use super::*; + use crate::block::BlockKind; #[test] fn item_kind_to_block_kind() { @@ -160,3 +163,4 @@ mod tests { assert_eq!(stack.count, STACK_MAX); } } +*/ diff --git a/crates/valence_core/src/lib.rs b/crates/valence_core/src/lib.rs new file mode 100644 index 0000000..d4055e5 --- /dev/null +++ b/crates/valence_core/src/lib.rs @@ -0,0 +1,178 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] +#![allow(clippy::unusual_byte_groupings)] + +pub mod aabb; +pub mod block_pos; +pub mod chunk_pos; +pub mod despawn; +pub mod difficulty; +pub mod direction; +pub mod game_mode; +pub mod hand; +pub mod ident; +pub mod item; +pub mod packet; +pub mod player_textures; +pub mod property; +pub mod scratch; +pub mod sound; +pub mod text; +pub mod translation_key; +pub mod uuid; + +use std::num::NonZeroU32; +use std::time::Duration; + +use bevy_app::prelude::*; +use bevy_app::{ScheduleRunnerPlugin, ScheduleRunnerSettings}; +use bevy_ecs::prelude::*; + +use crate::despawn::despawn_marked_entities; + +/// Used only by macros. Not public API. +#[doc(hidden)] +pub mod __private { + pub use anyhow::{anyhow, bail, ensure, Context, Result}; + + pub use crate::packet::var_int::VarInt; + pub use crate::packet::{Decode, Encode, Packet}; +} + +// Needed to make proc macros work. +extern crate self as valence_core; + +/// The Minecraft protocol version this library currently targets. +pub const PROTOCOL_VERSION: i32 = 762; + +/// The stringified name of the Minecraft version this library currently +/// targets. +pub const MINECRAFT_VERSION: &str = "1.19.4"; + +/// Minecraft's standard ticks per second (TPS). +pub const DEFAULT_TPS: NonZeroU32 = match NonZeroU32::new(20) { + Some(n) => n, + None => unreachable!(), +}; + +pub struct CorePlugin; + +impl Plugin for CorePlugin { + fn build(&self, app: &mut App) { + let settings = app.world.get_resource_or_insert_with(CoreSettings::default); + + let compression_threshold = settings.compression_threshold; + let tick_rate = settings.tick_rate; + + app.insert_resource(Server { + current_tick: 0, + compression_threshold, + }); + + let tick_period = Duration::from_secs_f64((tick_rate.get() as f64).recip()); + + // Make the app loop forever at the configured TPS. + app.insert_resource(ScheduleRunnerSettings::run_loop(tick_period)) + .add_plugin(ScheduleRunnerPlugin); + + fn increment_tick_counter(mut server: ResMut) { + server.current_tick += 1; + } + + app.add_systems( + (increment_tick_counter, despawn_marked_entities).in_base_set(CoreSet::Last), + ); + + // TODO: do this in `DefaultPlugins`. + /* + // Add internal plugins. + app.add_plugin(EventLoopPlugin) + .add_plugin(RegistryCodecPlugin) + .add_plugin(BiomePlugin) + .add_plugin(DimensionPlugin) + .add_plugin(ComponentPlugin) + .add_plugin(ClientPlugin) + .add_plugin(EntityPlugin) + .add_plugin(InstancePlugin) + .add_plugin(InventoryPlugin) + .add_plugin(PlayerListPlugin) + .add_plugin(WeatherPlugin); + */ + } +} + +#[derive(Resource, Debug)] +pub struct CoreSettings { + /// The target ticks per second (TPS) of the server. This is the number of + /// game updates that should occur in one second. + /// + /// On each game update (tick), the server is expected to update game logic + /// and respond to packets from clients. Once this is complete, the server + /// will sleep for any remaining time until a full tick duration has passed. + /// + /// Note that the official Minecraft client only processes packets at 20hz, + /// so there is little benefit to a tick rate higher than the default 20. + /// + /// # Default Value + /// + /// [`DEFAULT_TPS`] + pub tick_rate: NonZeroU32, + /// The compression threshold to use for compressing packets. For a + /// compression threshold of `Some(N)`, packets with encoded lengths >= `N` + /// are compressed while all others are not. `None` disables compression + /// completely. + /// + /// If the server is used behind a proxy on the same machine, you will + /// likely want to disable compression. + /// + /// # Default Value + /// + /// Compression is enabled with an unspecified value. This value may + /// change in future versions. + pub compression_threshold: Option, +} + +impl Default for CoreSettings { + fn default() -> Self { + Self { + tick_rate: DEFAULT_TPS, + compression_threshold: Some(256), + } + } +} + +/// Contains global server state accessible as a [`Resource`]. +#[derive(Resource)] +pub struct Server { + /// Incremented on every tick. + current_tick: i64, + compression_threshold: Option, +} + +impl Server { + /// Returns the number of ticks that have elapsed since the server began. + pub fn current_tick(&self) -> i64 { + self.current_tick + } + + /// Returns the server's compression threshold. + pub fn compression_threshold(&self) -> Option { + self.compression_threshold + } +} diff --git a/crates/valence_protocol/src/lib.rs b/crates/valence_core/src/packet.rs similarity index 51% rename from crates/valence_protocol/src/lib.rs rename to crates/valence_core/src/packet.rs index edb0fda..16d22be 100644 --- a/crates/valence_protocol/src/lib.rs +++ b/crates/valence_core/src/packet.rs @@ -1,117 +1,23 @@ -//! A library for interacting with the Minecraft (Java Edition) network -//! protocol. +//! Types and functions used in Minecraft's packets. Structs for each packet are +//! defined here too. //! -//! The API is centered around the [`Encode`] and [`Decode`] traits. Clientbound -//! and serverbound packets are defined in the [`packet`] module. Packets are -//! encoded and decoded using the [`PacketEncoder`] and [`PacketDecoder`] types. -//! -//! [`PacketEncoder`]: encoder::PacketEncoder -//! [`PacketDecoder`]: decoder::PacketDecoder -//! -//! # Examples -//! -//! ``` -//! use valence_protocol::decoder::PacketDecoder; -//! use valence_protocol::encoder::PacketEncoder; -//! use valence_protocol::packet::c2s::play::RenameItemC2s; -//! use valence_protocol::Packet; -//! -//! let mut enc = PacketEncoder::new(); -//! -//! let outgoing = RenameItemC2s { -//! item_name: "Hello!", -//! }; -//! -//! enc.append_packet(&outgoing).unwrap(); -//! -//! let mut dec = PacketDecoder::new(); -//! -//! dec.queue_bytes(enc.take()); -//! -//! let frame = dec.try_next_packet().unwrap().unwrap(); -//! -//! let incoming = RenameItemC2s::decode_packet(&mut &frame[..]).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 - -#![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::unusual_byte_groupings)] - -// Allows us to use our own proc macros internally. -extern crate self as valence_protocol; - -use std::fmt; -use std::io::Write; - -pub use anyhow::{Error, Result}; -pub use valence_protocol_macros::{ident, Decode, Encode, Packet}; -pub use {bytes, uuid, valence_nbt as nbt}; - -/// The Minecraft protocol version this library currently targets. -pub const PROTOCOL_VERSION: i32 = 762; - -/// The stringified name of the Minecraft version this library currently -/// targets. -pub const MINECRAFT_VERSION: &str = "1.19.4"; +//! Client-to-server packets live in [`c2s`] while server-to-client packets are +//! in [`s2c`]. pub mod array; -pub mod block; -pub mod block_pos; pub mod byte_angle; -pub mod decoder; -pub mod enchant; -pub mod encoder; -pub mod ident; -mod impls; -pub mod item; -pub mod packet; +pub mod decode; +pub mod encode; +pub mod global_pos; +pub mod impls; +pub mod message_signature; pub mod raw; -pub mod sound; -pub mod text; -pub mod translation_key; -pub mod types; 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}; +use std::io::Write; - pub use crate::var_int::VarInt; - pub use crate::{Decode, Encode, Packet}; -} +pub use valence_core_macros::{Decode, Encode, Packet}; /// The maximum number of bytes in a single Minecraft packet. pub const MAX_PACKET_SIZE: i32 = 2097152; @@ -132,7 +38,7 @@ pub const MAX_PACKET_SIZE: i32 = 2097152; /// to variants using rules similar to regular enum discriminants. /// /// ``` -/// use valence_protocol::Encode; +/// use valence_core::packet::Encode; /// /// #[derive(Encode)] /// struct MyStruct<'a> { @@ -162,7 +68,7 @@ pub const MAX_PACKET_SIZE: i32 = 2097152; /// println!("{buf:?}"); /// ``` /// -/// [macro]: valence_protocol_macros::Encode +/// [macro]: valence_core_macros::Encode /// [`VarInt`]: var_int::VarInt pub trait Encode { /// Writes this object to the provided writer. @@ -173,21 +79,30 @@ pub trait Encode { /// that were originally written must be consumed during the decoding. /// /// [`decode`]: Decode::decode - fn encode(&self, w: impl Write) -> Result<()>; + fn encode(&self, w: impl Write) -> anyhow::Result<()>; - /// Hack to get around the lack of specialization. Not public API. - #[doc(hidden)] - fn encode_slice(slice: &[Self], w: impl Write) -> Result<()> + /// Like [`Encode::encode`], except that a whole slice of values is encoded. + /// + /// This method must be semantically equivalent to encoding every element of + /// the slice in sequence with no leading length prefix (which is exactly + /// what the default implementation does), but a more efficient + /// implementation may be used. + /// + /// This optimization is very important for some types like `u8` where + /// [`write_all`] is used. Because impl specialization is unavailable in + /// stable Rust, we must make the slice specialization part of this trait. + /// + /// [`write_all`]: Write::write_all + fn encode_slice(slice: &[Self], mut w: impl Write) -> anyhow::Result<()> where Self: Sized, { - let _ = (slice, w); - unimplemented!("no implementation of `encode_slice`") - } + for value in slice { + value.encode(&mut w)?; + } - /// Hack to get around the lack of specialization. Not public API. - #[doc(hidden)] - const HAS_ENCODE_SLICE: bool = false; + Ok(()) + } } /// The `Decode` trait allows objects to be read from the Minecraft protocol. It @@ -209,7 +124,7 @@ pub trait Encode { /// to variants using rules similar to regular enum discriminants. /// /// ``` -/// use valence_protocol::Decode; +/// use valence_core::packet::Decode; /// /// #[derive(PartialEq, Debug, Decode)] /// struct MyStruct { @@ -238,14 +153,14 @@ pub trait Encode { /// assert!(r.is_empty()); /// ``` /// -/// [macro]: valence_protocol_macros::Decode +/// [macro]: valence_core_macros::Decode /// [`VarInt`]: var_int::VarInt 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; + fn decode(r: &mut &'a [u8]) -> anyhow::Result; } /// Like [`Encode`] + [`Decode`], but implementations must read and write a @@ -257,10 +172,10 @@ pub trait Decode<'a>: Sized { /// [`Packet`][macro] derive macro. The trait is implemented by reading or /// writing the packet ID provided in the `#[packet_id = ...]` helper attribute /// followed by a call to [`Encode::encode`] or [`Decode::decode`]. The target -/// type must implement [`Encode`], [`Decode`], and [`fmt::Debug`]. +/// type must implement [`Encode`], [`Decode`], and [`std::fmt::Debug`]. /// /// ``` -/// use valence_protocol::{Decode, Encode, Packet}; +/// use valence_core::packet::{Decode, Encode, Packet}; /// /// #[derive(Encode, Decode, Packet, Debug)] /// #[packet_id = 42] @@ -275,9 +190,9 @@ pub trait Decode<'a>: Sized { /// println!("{buf:?}"); /// ``` /// -/// [macro]: valence_protocol_macros::Packet +/// [macro]: valence_core_macros::Packet /// [`VarInt`]: var_int::VarInt -pub trait Packet<'a>: Sized + fmt::Debug { +pub trait Packet<'a>: Sized + std::fmt::Debug { /// The packet returned by [`Self::packet_id`]. If the packet ID is not /// statically known, then a negative value is used instead. const PACKET_ID: i32 = -1; @@ -287,15 +202,251 @@ pub trait Packet<'a>: Sized + fmt::Debug { /// additional formatting. fn packet_name(&self) -> &str; /// Like [`Encode::encode`], but a leading [`VarInt`] packet ID must be - /// written first. + /// written by this method first. /// /// [`VarInt`]: var_int::VarInt - fn encode_packet(&self, w: impl Write) -> Result<()>; + fn encode_packet(&self, w: impl Write) -> anyhow::Result<()>; /// Like [`Decode::decode`], but a leading [`VarInt`] packet ID must be read - /// first. + /// by this method first. /// /// [`VarInt`]: var_int::VarInt - fn decode_packet(r: &mut &'a [u8]) -> Result; + fn decode_packet(r: &mut &'a [u8]) -> anyhow::Result; +} + +/// Defines an enum of packets and implements [`Packet`] for each. +macro_rules! packet_group { + ( + $(#[$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::packet::Packet<$enum_life> for $packet$(<$life>)? { + const PACKET_ID: i32 = $crate::packet::id::$packet; + + fn packet_id(&self) -> i32 { + Self::PACKET_ID + } + + fn packet_name(&self) -> &str { + stringify!($packet) + } + + #[allow(unused_imports)] + fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> { + use $crate::__private::*; + + VarInt(Self::PACKET_ID) + .encode(&mut w) + .context("failed to encode packet ID")?; + + self.encode(w) + } + + #[allow(unused_imports)] + fn decode_packet(r: &mut &$enum_life [u8]) -> $crate::__private::Result { + use $crate::__private::*; + + let id = VarInt::decode(r).context("failed to decode packet ID")?.0; + ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID); + + Self::decode(r) + } + } + )* + + impl<$enum_life> $crate::packet::Packet<$enum_life> for $enum_name<$enum_life> { + fn packet_id(&self) -> i32 { + match self { + $( + Self::$packet(_) => <$packet as $crate::packet::Packet>::PACKET_ID, + )* + } + } + + fn packet_name(&self) -> &str { + match self { + $( + Self::$packet(pkt) => pkt.packet_name(), + )* + } + } + + fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> { + use $crate::__private::*; + + match self { + $( + Self::$packet(pkt) => { + VarInt(<$packet as Packet>::PACKET_ID).encode(&mut w)?; + pkt.encode(w)?; + } + )* + } + + Ok(()) + } + + fn decode_packet(r: &mut &$enum_life [u8]) -> $crate::__private::Result { + use $crate::__private::*; + + let id = VarInt::decode(r)?.0; + Ok(match id { + $( + <$packet as Packet>::PACKET_ID => + Self::$packet($packet::decode(r)?), + )* + id => bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)), + }) + } + } + + impl<$enum_life> std::fmt::Debug for $enum_name<$enum_life> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + $( + Self::$packet(pkt) => pkt.fmt(f), + )* + } + } + } + }; + // 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::__private::Packet<'_> for $packet { + const PACKET_ID: i32 = $crate::packet::id::$packet; + + fn packet_id(&self) -> i32 { + Self::PACKET_ID + } + + fn packet_name(&self) -> &str { + stringify!($packet) + } + + #[allow(unused_imports)] + fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> { + use $crate::__private::*; + + VarInt(Self::PACKET_ID) + .encode(&mut w) + .context("failed to encode packet ID")?; + + self.encode(w) + } + + #[allow(unused_imports)] + fn decode_packet(r: &mut &[u8]) -> $crate::__private::Result { + use $crate::__private::*; + + let id = VarInt::decode(r).context("failed to decode packet ID")?.0; + ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID); + + Self::decode(r) + } + } + )* + + impl $crate::__private::Packet<'_> for $enum_name { + fn packet_id(&self) -> i32 { + use $crate::__private::*; + + match self { + $( + Self::$packet(_) => <$packet as Packet>::PACKET_ID, + )* + } + } + + fn packet_name(&self) -> &str { + match self { + $( + Self::$packet(pkt) => pkt.packet_name(), + )* + } + } + + fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> { + use $crate::__private::*; + + match self { + $( + Self::$packet(pkt) => { + VarInt(<$packet as Packet>::PACKET_ID).encode(&mut w)?; + pkt.encode(w)?; + } + )* + } + + Ok(()) + } + + fn decode_packet(r: &mut &[u8]) -> $crate::__private::Result { + use $crate::__private::*; + + let id = VarInt::decode(r)?.0; + Ok(match id { + $( + <$packet as Packet>::PACKET_ID => + Self::$packet($packet::decode(r)?), + )* + id => anyhow::bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)), + }) + } + } + + impl std::fmt::Debug for $enum_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + $( + Self::$packet(pkt) => pkt.fmt(f), + )* + } + } + } + } +} + +pub mod c2s; +pub mod s2c; + +/// Contains the packet ID for every packet. Because the constants are private +/// to the crate, the compiler will yell at us when we forget to use one. +mod id { + include!(concat!(env!("OUT_DIR"), "/packet_id.rs")); } #[allow(dead_code)] @@ -306,10 +457,9 @@ mod tests { use bytes::BytesMut; use super::*; - use crate::decoder::{decode_packet, PacketDecoder}; - use crate::encoder::PacketEncoder; - use crate::packet::c2s::play::HandSwingC2s; - use crate::packet::C2sPlayPacket; + use crate::packet::c2s::play::{C2sPlayPacket, HandSwingC2s}; + use crate::packet::decode::{decode_packet, PacketDecoder}; + use crate::packet::encode::PacketEncoder; #[derive(Encode, Decode, Packet, Debug)] #[packet_id = 1] @@ -368,7 +518,7 @@ mod tests { Fourth(T), } - #[allow(unconditional_recursion)] + #[allow(unconditional_recursion, clippy::extra_unused_type_parameters)] fn assert_has_impls<'a, T>() where T: Encode + Decode<'a> + Packet<'a>, @@ -406,12 +556,12 @@ mod tests { } use crate::block_pos::BlockPos; + use crate::hand::Hand; use crate::ident::Ident; use crate::item::{ItemKind, ItemStack}; + use crate::packet::var_int::VarInt; + use crate::packet::var_long::VarLong; use crate::text::{Text, TextFormat}; - use crate::types::Hand; - use crate::var_int::VarInt; - 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]; diff --git a/crates/valence_protocol/src/array.rs b/crates/valence_core/src/packet/array.rs similarity index 94% rename from crates/valence_protocol/src/array.rs rename to crates/valence_core/src/packet/array.rs index c2253fd..d8c1b0d 100644 --- a/crates/valence_protocol/src/array.rs +++ b/crates/valence_core/src/packet/array.rs @@ -2,8 +2,8 @@ use std::io::Write; use anyhow::ensure; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; /// A fixed-size array encoded and decoded with a [`VarInt`] length prefix. /// diff --git a/crates/valence_protocol/src/byte_angle.rs b/crates/valence_core/src/packet/byte_angle.rs similarity index 83% rename from crates/valence_protocol/src/byte_angle.rs rename to crates/valence_core/src/packet/byte_angle.rs index 1f8dc5b..c6a213d 100644 --- a/crates/valence_protocol/src/byte_angle.rs +++ b/crates/valence_core/src/packet/byte_angle.rs @@ -1,7 +1,7 @@ use std::f32::consts::TAU; use std::io::Write; -use crate::{Decode, Encode, Result}; +use crate::packet::{Decode, Encode}; /// Represents an angle in steps of 1/256 of a full turn. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -26,13 +26,13 @@ impl ByteAngle { } impl Encode for ByteAngle { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { self.0.encode(w) } } impl Decode<'_> for ByteAngle { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { u8::decode(r).map(ByteAngle) } } diff --git a/crates/valence_protocol/src/packet/c2s.rs b/crates/valence_core/src/packet/c2s.rs similarity index 100% rename from crates/valence_protocol/src/packet/c2s.rs rename to crates/valence_core/src/packet/c2s.rs diff --git a/crates/valence_protocol/src/packet/c2s/handshake/handshake.rs b/crates/valence_core/src/packet/c2s/handshake/handshake.rs similarity index 82% rename from crates/valence_protocol/src/packet/c2s/handshake/handshake.rs rename to crates/valence_core/src/packet/c2s/handshake/handshake.rs index 8158e24..6b14d97 100644 --- a/crates/valence_protocol/src/packet/c2s/handshake/handshake.rs +++ b/crates/valence_core/src/packet/c2s/handshake/handshake.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct HandshakeC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/login/login_hello.rs b/crates/valence_core/src/packet/c2s/login/login_hello.rs similarity index 82% rename from crates/valence_protocol/src/packet/c2s/login/login_hello.rs rename to crates/valence_core/src/packet/c2s/login/login_hello.rs index b1352ae..ff5046b 100644 --- a/crates/valence_protocol/src/packet/c2s/login/login_hello.rs +++ b/crates/valence_core/src/packet/c2s/login/login_hello.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct LoginHelloC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/login/login_key.rs b/crates/valence_core/src/packet/c2s/login/login_key.rs similarity index 78% rename from crates/valence_protocol/src/packet/c2s/login/login_key.rs rename to crates/valence_core/src/packet/c2s/login/login_key.rs index f7f1eb2..d89b7fa 100644 --- a/crates/valence_protocol/src/packet/c2s/login/login_key.rs +++ b/crates/valence_core/src/packet/c2s/login/login_key.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct LoginKeyC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/login/login_query_response.rs b/crates/valence_core/src/packet/c2s/login/login_query_response.rs similarity index 57% rename from crates/valence_protocol/src/packet/c2s/login/login_query_response.rs rename to crates/valence_core/src/packet/c2s/login/login_query_response.rs index 07cf818..a202543 100644 --- a/crates/valence_protocol/src/packet/c2s/login/login_query_response.rs +++ b/crates/valence_core/src/packet/c2s/login/login_query_response.rs @@ -1,6 +1,6 @@ -use crate::raw::RawBytes; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::raw::RawBytes; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct LoginQueryResponseC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/advancement_tab.rs b/crates/valence_core/src/packet/c2s/play/advancement_tab.rs similarity index 83% rename from crates/valence_protocol/src/packet/c2s/play/advancement_tab.rs rename to crates/valence_core/src/packet/c2s/play/advancement_tab.rs index 778ee90..b7ffadc 100644 --- a/crates/valence_protocol/src/packet/c2s/play/advancement_tab.rs +++ b/crates/valence_core/src/packet/c2s/play/advancement_tab.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub enum AdvancementTabC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/boat_paddle.rs b/crates/valence_core/src/packet/c2s/play/boat_paddle.rs similarity index 80% rename from crates/valence_protocol/src/packet/c2s/play/boat_paddle.rs rename to crates/valence_core/src/packet/c2s/play/boat_paddle.rs index b8f359d..edbefef 100644 --- a/crates/valence_protocol/src/packet/c2s/play/boat_paddle.rs +++ b/crates/valence_core/src/packet/c2s/play/boat_paddle.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct BoatPaddleStateC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/book_update.rs b/crates/valence_core/src/packet/c2s/play/book_update.rs similarity index 68% rename from crates/valence_protocol/src/packet/c2s/play/book_update.rs rename to crates/valence_core/src/packet/c2s/play/book_update.rs index 9ecc5d9..bdd54cc 100644 --- a/crates/valence_protocol/src/packet/c2s/play/book_update.rs +++ b/crates/valence_core/src/packet/c2s/play/book_update.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct BookUpdateC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/button_click.rs b/crates/valence_core/src/packet/c2s/play/button_click.rs similarity index 76% rename from crates/valence_protocol/src/packet/c2s/play/button_click.rs rename to crates/valence_core/src/packet/c2s/play/button_click.rs index d218d5f..83a537b 100644 --- a/crates/valence_protocol/src/packet/c2s/play/button_click.rs +++ b/crates/valence_core/src/packet/c2s/play/button_click.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ButtonClickC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/chat_message.rs b/crates/valence_core/src/packet/c2s/play/chat_message.rs similarity index 85% rename from crates/valence_protocol/src/packet/c2s/play/chat_message.rs rename to crates/valence_core/src/packet/c2s/play/chat_message.rs index 1e23ace..a2e9d95 100644 --- a/crates/valence_protocol/src/packet/c2s/play/chat_message.rs +++ b/crates/valence_core/src/packet/c2s/play/chat_message.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ChatMessageC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/click_slot.rs b/crates/valence_core/src/packet/c2s/play/click_slot.rs similarity index 90% rename from crates/valence_protocol/src/packet/c2s/play/click_slot.rs rename to crates/valence_core/src/packet/c2s/play/click_slot.rs index 1d6d76b..ad03ce6 100644 --- a/crates/valence_protocol/src/packet/c2s/play/click_slot.rs +++ b/crates/valence_core/src/packet/c2s/play/click_slot.rs @@ -1,6 +1,6 @@ use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ClickSlotC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/client_command.rs b/crates/valence_core/src/packet/c2s/play/client_command.rs similarity index 85% rename from crates/valence_protocol/src/packet/c2s/play/client_command.rs rename to crates/valence_core/src/packet/c2s/play/client_command.rs index f8e400e..8d785e9 100644 --- a/crates/valence_protocol/src/packet/c2s/play/client_command.rs +++ b/crates/valence_core/src/packet/c2s/play/client_command.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ClientCommandC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/client_settings.rs b/crates/valence_core/src/packet/c2s/play/client_settings.rs similarity index 96% rename from crates/valence_protocol/src/packet/c2s/play/client_settings.rs rename to crates/valence_core/src/packet/c2s/play/client_settings.rs index c2ba747..1dfc7c9 100644 --- a/crates/valence_protocol/src/packet/c2s/play/client_settings.rs +++ b/crates/valence_core/src/packet/c2s/play/client_settings.rs @@ -1,6 +1,6 @@ use bitfield_struct::bitfield; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ClientSettingsC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/client_status.rs b/crates/valence_core/src/packet/c2s/play/client_status.rs similarity index 74% rename from crates/valence_protocol/src/packet/c2s/play/client_status.rs rename to crates/valence_core/src/packet/c2s/play/client_status.rs index d7184e9..4a95300 100644 --- a/crates/valence_protocol/src/packet/c2s/play/client_status.rs +++ b/crates/valence_core/src/packet/c2s/play/client_status.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub enum ClientStatusC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/close_handled_screen.rs b/crates/valence_core/src/packet/c2s/play/close_handled_screen.rs similarity index 74% rename from crates/valence_protocol/src/packet/c2s/play/close_handled_screen.rs rename to crates/valence_core/src/packet/c2s/play/close_handled_screen.rs index 75ab6ef..6b14123 100644 --- a/crates/valence_protocol/src/packet/c2s/play/close_handled_screen.rs +++ b/crates/valence_core/src/packet/c2s/play/close_handled_screen.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct CloseHandledScreenC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/command_execution.rs b/crates/valence_core/src/packet/c2s/play/command_execution.rs similarity index 89% rename from crates/valence_protocol/src/packet/c2s/play/command_execution.rs rename to crates/valence_core/src/packet/c2s/play/command_execution.rs index 69f5835..a7285b3 100644 --- a/crates/valence_protocol/src/packet/c2s/play/command_execution.rs +++ b/crates/valence_core/src/packet/c2s/play/command_execution.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CommandExecutionC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/craft_request.rs b/crates/valence_core/src/packet/c2s/play/craft_request.rs similarity index 84% rename from crates/valence_protocol/src/packet/c2s/play/craft_request.rs rename to crates/valence_core/src/packet/c2s/play/craft_request.rs index 86af0b8..b762f4e 100644 --- a/crates/valence_protocol/src/packet/c2s/play/craft_request.rs +++ b/crates/valence_core/src/packet/c2s/play/craft_request.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CraftRequestC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/creative_inventory_action.rs b/crates/valence_core/src/packet/c2s/play/creative_inventory_action.rs similarity index 82% rename from crates/valence_protocol/src/packet/c2s/play/creative_inventory_action.rs rename to crates/valence_core/src/packet/c2s/play/creative_inventory_action.rs index 2e122d2..6d1f8f9 100644 --- a/crates/valence_protocol/src/packet/c2s/play/creative_inventory_action.rs +++ b/crates/valence_core/src/packet/c2s/play/creative_inventory_action.rs @@ -1,5 +1,5 @@ use crate::item::ItemStack; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CreativeInventoryActionC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/custom_payload.rs b/crates/valence_core/src/packet/c2s/play/custom_payload.rs similarity index 72% rename from crates/valence_protocol/src/packet/c2s/play/custom_payload.rs rename to crates/valence_core/src/packet/c2s/play/custom_payload.rs index 659a04f..e1ab154 100644 --- a/crates/valence_protocol/src/packet/c2s/play/custom_payload.rs +++ b/crates/valence_core/src/packet/c2s/play/custom_payload.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::raw::RawBytes; -use crate::{Decode, Encode}; +use crate::packet::raw::RawBytes; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CustomPayloadC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/hand_swing.rs b/crates/valence_core/src/packet/c2s/play/hand_swing.rs similarity index 61% rename from crates/valence_protocol/src/packet/c2s/play/hand_swing.rs rename to crates/valence_core/src/packet/c2s/play/hand_swing.rs index ca0a11d..fe2238d 100644 --- a/crates/valence_protocol/src/packet/c2s/play/hand_swing.rs +++ b/crates/valence_core/src/packet/c2s/play/hand_swing.rs @@ -1,5 +1,5 @@ -use crate::types::Hand; -use crate::{Decode, Encode}; +use crate::hand::Hand; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct HandSwingC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/jigsaw_generating.rs b/crates/valence_core/src/packet/c2s/play/jigsaw_generating.rs similarity index 72% rename from crates/valence_protocol/src/packet/c2s/play/jigsaw_generating.rs rename to crates/valence_core/src/packet/c2s/play/jigsaw_generating.rs index 30ac538..c67a728 100644 --- a/crates/valence_protocol/src/packet/c2s/play/jigsaw_generating.rs +++ b/crates/valence_core/src/packet/c2s/play/jigsaw_generating.rs @@ -1,6 +1,6 @@ use crate::block_pos::BlockPos; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct JigsawGeneratingC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/keep_alive.rs b/crates/valence_core/src/packet/c2s/play/keep_alive.rs similarity index 71% rename from crates/valence_protocol/src/packet/c2s/play/keep_alive.rs rename to crates/valence_core/src/packet/c2s/play/keep_alive.rs index 2969701..8e6ced7 100644 --- a/crates/valence_protocol/src/packet/c2s/play/keep_alive.rs +++ b/crates/valence_core/src/packet/c2s/play/keep_alive.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct KeepAliveC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/message_acknowledgment.rs b/crates/valence_core/src/packet/c2s/play/message_acknowledgment.rs similarity index 61% rename from crates/valence_protocol/src/packet/c2s/play/message_acknowledgment.rs rename to crates/valence_core/src/packet/c2s/play/message_acknowledgment.rs index dff0245..41924d8 100644 --- a/crates/valence_protocol/src/packet/c2s/play/message_acknowledgment.rs +++ b/crates/valence_core/src/packet/c2s/play/message_acknowledgment.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct MessageAcknowledgmentC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/pick_from_inventory.rs b/crates/valence_core/src/packet/c2s/play/pick_from_inventory.rs similarity index 60% rename from crates/valence_protocol/src/packet/c2s/play/pick_from_inventory.rs rename to crates/valence_core/src/packet/c2s/play/pick_from_inventory.rs index 702333d..7e0d6dc 100644 --- a/crates/valence_protocol/src/packet/c2s/play/pick_from_inventory.rs +++ b/crates/valence_core/src/packet/c2s/play/pick_from_inventory.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PickFromInventoryC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/play_pong.rs b/crates/valence_core/src/packet/c2s/play/play_pong.rs similarity index 71% rename from crates/valence_protocol/src/packet/c2s/play/play_pong.rs rename to crates/valence_core/src/packet/c2s/play/play_pong.rs index 794dc62..7b6be2f 100644 --- a/crates/valence_protocol/src/packet/c2s/play/play_pong.rs +++ b/crates/valence_core/src/packet/c2s/play/play_pong.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayPongC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/player_action.rs b/crates/valence_core/src/packet/c2s/play/player_action.rs similarity index 80% rename from crates/valence_protocol/src/packet/c2s/play/player_action.rs rename to crates/valence_core/src/packet/c2s/play/player_action.rs index 0221252..6c50678 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_action.rs +++ b/crates/valence_core/src/packet/c2s/play/player_action.rs @@ -1,7 +1,7 @@ use crate::block_pos::BlockPos; -use crate::types::Direction; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::direction::Direction; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerActionC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/player_input.rs b/crates/valence_core/src/packet/c2s/play/player_input.rs similarity index 89% rename from crates/valence_protocol/src/packet/c2s/play/player_input.rs rename to crates/valence_core/src/packet/c2s/play/player_input.rs index 9121851..6772a52 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_input.rs +++ b/crates/valence_core/src/packet/c2s/play/player_input.rs @@ -1,6 +1,6 @@ use bitfield_struct::bitfield; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerInputC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/player_interact_block.rs b/crates/valence_core/src/packet/c2s/play/player_interact_block.rs similarity index 59% rename from crates/valence_protocol/src/packet/c2s/play/player_interact_block.rs rename to crates/valence_core/src/packet/c2s/play/player_interact_block.rs index 78ad602..9fe96df 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_interact_block.rs +++ b/crates/valence_core/src/packet/c2s/play/player_interact_block.rs @@ -1,14 +1,17 @@ +use glam::Vec3; + use crate::block_pos::BlockPos; -use crate::types::{Direction, Hand}; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::direction::Direction; +use crate::hand::Hand; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerInteractBlockC2s { pub hand: Hand, pub position: BlockPos, pub face: Direction, - pub cursor_pos: [f32; 3], + pub cursor_pos: Vec3, pub head_inside_block: bool, pub sequence: VarInt, } diff --git a/crates/valence_protocol/src/packet/c2s/play/player_interact_entity.rs b/crates/valence_core/src/packet/c2s/play/player_interact_entity.rs similarity index 65% rename from crates/valence_protocol/src/packet/c2s/play/player_interact_entity.rs rename to crates/valence_core/src/packet/c2s/play/player_interact_entity.rs index 5294fb5..4559407 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_interact_entity.rs +++ b/crates/valence_core/src/packet/c2s/play/player_interact_entity.rs @@ -1,6 +1,8 @@ -use crate::types::Hand; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use glam::Vec3; + +use crate::hand::Hand; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerInteractEntityC2s { @@ -13,5 +15,5 @@ pub struct PlayerInteractEntityC2s { pub enum EntityInteraction { Interact(Hand), Attack, - InteractAt { target: [f32; 3], hand: Hand }, + InteractAt { target: Vec3, hand: Hand }, } diff --git a/crates/valence_protocol/src/packet/c2s/play/player_interact_item.rs b/crates/valence_core/src/packet/c2s/play/player_interact_item.rs similarity index 57% rename from crates/valence_protocol/src/packet/c2s/play/player_interact_item.rs rename to crates/valence_core/src/packet/c2s/play/player_interact_item.rs index 19770bb..437f184 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_interact_item.rs +++ b/crates/valence_core/src/packet/c2s/play/player_interact_item.rs @@ -1,6 +1,6 @@ -use crate::types::Hand; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::hand::Hand; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerInteractItemC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/player_move.rs b/crates/valence_core/src/packet/c2s/play/player_move.rs similarity index 81% rename from crates/valence_protocol/src/packet/c2s/play/player_move.rs rename to crates/valence_core/src/packet/c2s/play/player_move.rs index 347831d..492ef30 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_move.rs +++ b/crates/valence_core/src/packet/c2s/play/player_move.rs @@ -1,14 +1,16 @@ -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PositionAndOnGround { - pub position: [f64; 3], + pub position: DVec3, pub on_ground: bool, } #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct Full { - pub position: [f64; 3], + pub position: DVec3, pub yaw: f32, pub pitch: f32, pub on_ground: bool, diff --git a/crates/valence_protocol/src/packet/c2s/play/player_session.rs b/crates/valence_core/src/packet/c2s/play/player_session.rs similarity index 86% rename from crates/valence_protocol/src/packet/c2s/play/player_session.rs rename to crates/valence_core/src/packet/c2s/play/player_session.rs index e46e2fc..f97c5fb 100644 --- a/crates/valence_protocol/src/packet/c2s/play/player_session.rs +++ b/crates/valence_core/src/packet/c2s/play/player_session.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerSessionC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/query_block_nbt.rs b/crates/valence_core/src/packet/c2s/play/query_block_nbt.rs similarity index 70% rename from crates/valence_protocol/src/packet/c2s/play/query_block_nbt.rs rename to crates/valence_core/src/packet/c2s/play/query_block_nbt.rs index 91cb059..d20c570 100644 --- a/crates/valence_protocol/src/packet/c2s/play/query_block_nbt.rs +++ b/crates/valence_core/src/packet/c2s/play/query_block_nbt.rs @@ -1,6 +1,6 @@ use crate::block_pos::BlockPos; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct QueryBlockNbtC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/query_entity_nbt.rs b/crates/valence_core/src/packet/c2s/play/query_entity_nbt.rs similarity index 65% rename from crates/valence_protocol/src/packet/c2s/play/query_entity_nbt.rs rename to crates/valence_core/src/packet/c2s/play/query_entity_nbt.rs index e45f0fc..4476680 100644 --- a/crates/valence_protocol/src/packet/c2s/play/query_entity_nbt.rs +++ b/crates/valence_core/src/packet/c2s/play/query_entity_nbt.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct QueryEntityNbtC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/recipe_book_data.rs b/crates/valence_core/src/packet/c2s/play/recipe_book_data.rs similarity index 81% rename from crates/valence_protocol/src/packet/c2s/play/recipe_book_data.rs rename to crates/valence_core/src/packet/c2s/play/recipe_book_data.rs index 23a4fee..e291704 100644 --- a/crates/valence_protocol/src/packet/c2s/play/recipe_book_data.rs +++ b/crates/valence_core/src/packet/c2s/play/recipe_book_data.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct RecipeBookDataC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/recipe_category_options.rs b/crates/valence_core/src/packet/c2s/play/recipe_category_options.rs similarity index 89% rename from crates/valence_protocol/src/packet/c2s/play/recipe_category_options.rs rename to crates/valence_core/src/packet/c2s/play/recipe_category_options.rs index 351e96d..2abcebe 100644 --- a/crates/valence_protocol/src/packet/c2s/play/recipe_category_options.rs +++ b/crates/valence_core/src/packet/c2s/play/recipe_category_options.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct RecipeCategoryOptionsC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/rename_item.rs b/crates/valence_core/src/packet/c2s/play/rename_item.rs similarity index 74% rename from crates/valence_protocol/src/packet/c2s/play/rename_item.rs rename to crates/valence_core/src/packet/c2s/play/rename_item.rs index 996f078..82f2fc6 100644 --- a/crates/valence_protocol/src/packet/c2s/play/rename_item.rs +++ b/crates/valence_core/src/packet/c2s/play/rename_item.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct RenameItemC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/request_command_completions.rs b/crates/valence_core/src/packet/c2s/play/request_command_completions.rs similarity index 67% rename from crates/valence_protocol/src/packet/c2s/play/request_command_completions.rs rename to crates/valence_core/src/packet/c2s/play/request_command_completions.rs index c53356e..efc86e2 100644 --- a/crates/valence_protocol/src/packet/c2s/play/request_command_completions.rs +++ b/crates/valence_core/src/packet/c2s/play/request_command_completions.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct RequestCommandCompletionsC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/resource_pack_status.rs b/crates/valence_core/src/packet/c2s/play/resource_pack_status.rs similarity index 80% rename from crates/valence_protocol/src/packet/c2s/play/resource_pack_status.rs rename to crates/valence_core/src/packet/c2s/play/resource_pack_status.rs index 7860b08..752c1db 100644 --- a/crates/valence_protocol/src/packet/c2s/play/resource_pack_status.rs +++ b/crates/valence_core/src/packet/c2s/play/resource_pack_status.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub enum ResourcePackStatusC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/select_merchant_trade.rs b/crates/valence_core/src/packet/c2s/play/select_merchant_trade.rs similarity index 61% rename from crates/valence_protocol/src/packet/c2s/play/select_merchant_trade.rs rename to crates/valence_core/src/packet/c2s/play/select_merchant_trade.rs index c87a089..0b7602c 100644 --- a/crates/valence_protocol/src/packet/c2s/play/select_merchant_trade.rs +++ b/crates/valence_core/src/packet/c2s/play/select_merchant_trade.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct SelectMerchantTradeC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/spectator_teleport.rs b/crates/valence_core/src/packet/c2s/play/spectator_teleport.rs similarity index 76% rename from crates/valence_protocol/src/packet/c2s/play/spectator_teleport.rs rename to crates/valence_core/src/packet/c2s/play/spectator_teleport.rs index 0eb65f4..8931c1e 100644 --- a/crates/valence_protocol/src/packet/c2s/play/spectator_teleport.rs +++ b/crates/valence_core/src/packet/c2s/play/spectator_teleport.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct SpectatorTeleportC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/teleport_confirm.rs b/crates/valence_core/src/packet/c2s/play/teleport_confirm.rs similarity index 60% rename from crates/valence_protocol/src/packet/c2s/play/teleport_confirm.rs rename to crates/valence_core/src/packet/c2s/play/teleport_confirm.rs index 820d837..7c8cd73 100644 --- a/crates/valence_protocol/src/packet/c2s/play/teleport_confirm.rs +++ b/crates/valence_core/src/packet/c2s/play/teleport_confirm.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct TeleportConfirmC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_beacon.rs b/crates/valence_core/src/packet/c2s/play/update_beacon.rs similarity index 64% rename from crates/valence_protocol/src/packet/c2s/play/update_beacon.rs rename to crates/valence_core/src/packet/c2s/play/update_beacon.rs index d1e4826..de9688f 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_beacon.rs +++ b/crates/valence_core/src/packet/c2s/play/update_beacon.rs @@ -1,9 +1,8 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateBeaconC2s { - // TODO: extract effect IDs? pub primary_effect: Option, pub secondary_effect: Option, } diff --git a/crates/valence_protocol/src/packet/c2s/play/update_command_block.rs b/crates/valence_core/src/packet/c2s/play/update_command_block.rs similarity index 93% rename from crates/valence_protocol/src/packet/c2s/play/update_command_block.rs rename to crates/valence_core/src/packet/c2s/play/update_command_block.rs index e2dabe1..defed98 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_command_block.rs +++ b/crates/valence_core/src/packet/c2s/play/update_command_block.rs @@ -1,7 +1,7 @@ use bitfield_struct::bitfield; use crate::block_pos::BlockPos; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateCommandBlockC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_command_block_minecart.rs b/crates/valence_core/src/packet/c2s/play/update_command_block_minecart.rs similarity index 70% rename from crates/valence_protocol/src/packet/c2s/play/update_command_block_minecart.rs rename to crates/valence_core/src/packet/c2s/play/update_command_block_minecart.rs index b234098..897ad01 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_command_block_minecart.rs +++ b/crates/valence_core/src/packet/c2s/play/update_command_block_minecart.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateCommandBlockMinecartC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_difficulty.rs b/crates/valence_core/src/packet/c2s/play/update_difficulty.rs similarity index 64% rename from crates/valence_protocol/src/packet/c2s/play/update_difficulty.rs rename to crates/valence_core/src/packet/c2s/play/update_difficulty.rs index 78db016..f5451c0 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_difficulty.rs +++ b/crates/valence_core/src/packet/c2s/play/update_difficulty.rs @@ -1,5 +1,5 @@ -use crate::types::Difficulty; -use crate::{Decode, Encode}; +use crate::difficulty::Difficulty; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] pub struct UpdateDifficultyC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_difficulty_lock.rs b/crates/valence_core/src/packet/c2s/play/update_difficulty_lock.rs similarity index 74% rename from crates/valence_protocol/src/packet/c2s/play/update_difficulty_lock.rs rename to crates/valence_core/src/packet/c2s/play/update_difficulty_lock.rs index 979df8e..e40253c 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_difficulty_lock.rs +++ b/crates/valence_core/src/packet/c2s/play/update_difficulty_lock.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateDifficultyLockC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_jigsaw.rs b/crates/valence_core/src/packet/c2s/play/update_jigsaw.rs similarity index 90% rename from crates/valence_protocol/src/packet/c2s/play/update_jigsaw.rs rename to crates/valence_core/src/packet/c2s/play/update_jigsaw.rs index cbf5e77..4e1fed1 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_jigsaw.rs +++ b/crates/valence_core/src/packet/c2s/play/update_jigsaw.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use crate::block_pos::BlockPos; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct UpdateJigsawC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_player_abilities.rs b/crates/valence_core/src/packet/c2s/play/update_player_abilities.rs similarity index 80% rename from crates/valence_protocol/src/packet/c2s/play/update_player_abilities.rs rename to crates/valence_core/src/packet/c2s/play/update_player_abilities.rs index 3b248ab..8f96cee 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_player_abilities.rs +++ b/crates/valence_core/src/packet/c2s/play/update_player_abilities.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub enum UpdatePlayerAbilitiesC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_selected_slot.rs b/crates/valence_core/src/packet/c2s/play/update_selected_slot.rs similarity index 73% rename from crates/valence_protocol/src/packet/c2s/play/update_selected_slot.rs rename to crates/valence_core/src/packet/c2s/play/update_selected_slot.rs index 590da36..6cb7465 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_selected_slot.rs +++ b/crates/valence_core/src/packet/c2s/play/update_selected_slot.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateSelectedSlotC2s { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_sign.rs b/crates/valence_core/src/packet/c2s/play/update_sign.rs similarity index 82% rename from crates/valence_protocol/src/packet/c2s/play/update_sign.rs rename to crates/valence_core/src/packet/c2s/play/update_sign.rs index 2b129a0..f832bdc 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_sign.rs +++ b/crates/valence_core/src/packet/c2s/play/update_sign.rs @@ -1,5 +1,5 @@ use crate::block_pos::BlockPos; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateSignC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/update_structure_block.rs b/crates/valence_core/src/packet/c2s/play/update_structure_block.rs similarity index 94% rename from crates/valence_protocol/src/packet/c2s/play/update_structure_block.rs rename to crates/valence_core/src/packet/c2s/play/update_structure_block.rs index a523d29..316d9b5 100644 --- a/crates/valence_protocol/src/packet/c2s/play/update_structure_block.rs +++ b/crates/valence_core/src/packet/c2s/play/update_structure_block.rs @@ -1,8 +1,8 @@ use bitfield_struct::bitfield; use crate::block_pos::BlockPos; -use crate::var_long::VarLong; -use crate::{Decode, Encode}; +use crate::packet::var_long::VarLong; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateStructureBlockC2s<'a> { diff --git a/crates/valence_protocol/src/packet/c2s/play/vehicle_move.rs b/crates/valence_core/src/packet/c2s/play/vehicle_move.rs similarity index 58% rename from crates/valence_protocol/src/packet/c2s/play/vehicle_move.rs rename to crates/valence_core/src/packet/c2s/play/vehicle_move.rs index b7e05d0..d349307 100644 --- a/crates/valence_protocol/src/packet/c2s/play/vehicle_move.rs +++ b/crates/valence_core/src/packet/c2s/play/vehicle_move.rs @@ -1,8 +1,10 @@ -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct VehicleMoveC2s { - pub position: [f64; 3], + pub position: DVec3, pub yaw: f32, pub pitch: f32, } diff --git a/crates/valence_protocol/src/packet/c2s/status/query_ping.rs b/crates/valence_core/src/packet/c2s/status/query_ping.rs similarity index 72% rename from crates/valence_protocol/src/packet/c2s/status/query_ping.rs rename to crates/valence_core/src/packet/c2s/status/query_ping.rs index aef8910..08b1abe 100644 --- a/crates/valence_protocol/src/packet/c2s/status/query_ping.rs +++ b/crates/valence_core/src/packet/c2s/status/query_ping.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct QueryPingC2s { diff --git a/crates/valence_protocol/src/packet/c2s/status/query_request.rs b/crates/valence_core/src/packet/c2s/status/query_request.rs similarity index 66% rename from crates/valence_protocol/src/packet/c2s/status/query_request.rs rename to crates/valence_core/src/packet/c2s/status/query_request.rs index 6daf3ea..d09d009 100644 --- a/crates/valence_protocol/src/packet/c2s/status/query_request.rs +++ b/crates/valence_core/src/packet/c2s/status/query_request.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct QueryRequestC2s; diff --git a/crates/valence_protocol/src/decoder.rs b/crates/valence_core/src/packet/decode.rs similarity index 94% rename from crates/valence_protocol/src/decoder.rs rename to crates/valence_core/src/packet/decode.rs index 030b3c3..174a845 100644 --- a/crates/valence_protocol/src/decoder.rs +++ b/crates/valence_core/src/packet/decode.rs @@ -1,10 +1,10 @@ #[cfg(feature = "encryption")] use aes::cipher::{AsyncStreamCipher, NewCipher}; use anyhow::{bail, ensure}; -use bytes::{Buf, BufMut, BytesMut}; +use bytes::{Buf, BytesMut}; -use crate::var_int::{VarInt, VarIntDecodeError}; -use crate::{Packet, Result, MAX_PACKET_SIZE}; +use crate::packet::var_int::{VarInt, VarIntDecodeError}; +use crate::packet::{Packet, MAX_PACKET_SIZE}; /// The AES block cipher with a 128 bit key, using the CFB-8 mode of /// operation. @@ -27,7 +27,7 @@ impl PacketDecoder { Self::default() } - pub fn try_next_packet(&mut self) -> Result> { + pub fn try_next_packet(&mut self) -> anyhow::Result> { let mut r = &self.buf[..]; let packet_len = match VarInt::decode_partial(&mut r) { @@ -52,9 +52,10 @@ impl PacketDecoder { if let Some(threshold) = self.compression_threshold { use std::io::Write; + use bytes::BufMut; use flate2::write::ZlibDecoder; - use crate::Decode; + use crate::packet::Decode; r = &r[..packet_len as usize]; diff --git a/crates/valence_protocol/src/encoder.rs b/crates/valence_core/src/packet/encode.rs similarity index 76% rename from crates/valence_protocol/src/encoder.rs rename to crates/valence_core/src/packet/encode.rs index 5c26292..62bc4a1 100644 --- a/crates/valence_protocol/src/encoder.rs +++ b/crates/valence_core/src/packet/encode.rs @@ -1,8 +1,11 @@ +use std::io::Write; + use anyhow::ensure; use bytes::{BufMut, BytesMut}; +use tracing::warn; -use crate::var_int::VarInt; -use crate::{Encode, Packet, MAX_PACKET_SIZE}; +use crate::packet::var_int::VarInt; +use crate::packet::{Encode, Packet, MAX_PACKET_SIZE}; /// The AES block cipher with a 128 bit key, using the CFB-8 mode of /// operation. @@ -172,6 +175,79 @@ impl PacketEncoder { } } +/// Types that can have packets written to them. +pub trait WritePacket { + /// Writes a packet to this object. Encoding errors are typically logged and + /// discarded. + fn write_packet<'a>(&mut self, packet: &impl Packet<'a>); + /// Copies raw packet data directly into this object. Don't use this unless + /// you know what you're doing. + fn write_packet_bytes(&mut self, bytes: &[u8]); +} + +impl WritePacket for &mut W { + fn write_packet<'a>(&mut self, packet: &impl Packet<'a>) { + (*self).write_packet(packet) + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + (*self).write_packet_bytes(bytes) + } +} + +/// An implementor of [`WritePacket`] backed by a `Vec` reference. +pub struct PacketWriter<'a> { + pub buf: &'a mut Vec, + pub threshold: Option, + pub scratch: &'a mut Vec, +} + +impl<'a> PacketWriter<'a> { + pub fn new(buf: &'a mut Vec, threshold: Option, scratch: &'a mut Vec) -> Self { + Self { + buf, + threshold, + scratch, + } + } +} + +impl WritePacket for PacketWriter<'_> { + fn write_packet<'a>(&mut self, pkt: &impl Packet<'a>) { + #[cfg(feature = "compression")] + let res = if let Some(threshold) = self.threshold { + encode_packet_compressed(self.buf, pkt, threshold, self.scratch) + } else { + encode_packet(self.buf, pkt) + }; + + #[cfg(not(feature = "compression"))] + let res = encode_packet(self.buf, pkt); + + if let Err(e) = res { + warn!("failed to write packet: {e:#}"); + } + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + if let Err(e) = self.buf.write_all(bytes) { + warn!("failed to write packet bytes: {e:#}"); + } + } +} + +impl WritePacket for PacketEncoder { + fn write_packet<'a>(&mut self, packet: &impl Packet<'a>) { + if let Err(e) = self.append_packet(packet) { + warn!("failed to write packet: {e:#}"); + } + } + + fn write_packet_bytes(&mut self, bytes: &[u8]) { + self.append_bytes(bytes) + } +} + pub fn encode_packet<'a, P>(buf: &mut Vec, pkt: &P) -> anyhow::Result<()> where P: Packet<'a>, diff --git a/crates/valence_core/src/packet/global_pos.rs b/crates/valence_core/src/packet/global_pos.rs new file mode 100644 index 0000000..d0667a3 --- /dev/null +++ b/crates/valence_core/src/packet/global_pos.rs @@ -0,0 +1,11 @@ +use std::borrow::Cow; + +use crate::block_pos::BlockPos; +use crate::ident::Ident; +use crate::packet::{Decode, Encode}; + +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub struct GlobalPos<'a> { + pub dimension_name: Ident>, + pub position: BlockPos, +} diff --git a/crates/valence_protocol/src/impls.rs b/crates/valence_core/src/packet/impls.rs similarity index 78% rename from crates/valence_protocol/src/impls.rs rename to crates/valence_core/src/packet/impls.rs index 8c1bfcb..81aa9b4 100644 --- a/crates/valence_protocol/src/impls.rs +++ b/crates/valence_core/src/packet/impls.rs @@ -1,3 +1,5 @@ +//! [`Encode`] and [`Decode`] impls on foreign types. + use std::borrow::Cow; use std::collections::{BTreeSet, HashSet}; use std::hash::{BuildHasher, Hash}; @@ -7,13 +9,14 @@ use std::mem::MaybeUninit; use std::rc::Rc; use std::sync::Arc; -use anyhow::ensure; +use anyhow::{ensure, Result}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use glam::*; use uuid::Uuid; use valence_nbt::Compound; -use crate::var_int::VarInt; -use crate::{Decode, Encode, Result, MAX_PACKET_SIZE}; +use super::var_int::VarInt; +use super::{Decode, Encode, MAX_PACKET_SIZE}; // ==== Primitive ==== // @@ -28,8 +31,6 @@ impl Encode for bool { let bytes: &[u8] = unsafe { mem::transmute(slice) }; Ok(w.write_all(bytes)?) } - - const HAS_ENCODE_SLICE: bool = true; } impl Decode<'_> for bool { @@ -48,8 +49,6 @@ impl Encode for u8 { fn encode_slice(slice: &[u8], mut w: impl Write) -> Result<()> { Ok(w.write_all(slice)?) } - - const HAS_ENCODE_SLICE: bool = true; } impl Decode<'_> for u8 { @@ -68,8 +67,6 @@ impl Encode for i8 { let bytes: &[u8] = unsafe { mem::transmute(slice) }; Ok(w.write_all(bytes)?) } - - const HAS_ENCODE_SLICE: bool = true; } impl Decode<'_> for i8 { @@ -212,152 +209,165 @@ impl Decode<'_> for f64 { } } -#[cfg(feature = "glam")] -mod glam { - use ::glam::*; +// ==== Glam ==== // - use super::*; - - impl Encode for Vec2 { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(w) - } +impl Encode for Vec2 { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(w) } +} - impl Decode<'_> for Vec2 { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self { - x: f32::decode(r)?, - y: f32::decode(r)?, - }) - } +impl Decode<'_> for Vec2 { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self { + x: f32::decode(r)?, + y: f32::decode(r)?, + }) } +} - impl Encode for Vec3 { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(w) - } +impl Encode for Vec3 { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(w) } +} - impl Decode<'_> for Vec3 { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self { - x: f32::decode(r)?, - y: f32::decode(r)?, - z: f32::decode(r)?, - }) - } +impl Decode<'_> for Vec3 { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self { + x: f32::decode(r)?, + y: f32::decode(r)?, + z: f32::decode(r)?, + }) } +} - impl Encode for Vec3A { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(w) - } +impl Encode for Vec3A { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(w) } +} - impl Decode<'_> for Vec3A { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self::new(f32::decode(r)?, f32::decode(r)?, f32::decode(r)?)) - } +impl Decode<'_> for Vec3A { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self::new(f32::decode(r)?, f32::decode(r)?, f32::decode(r)?)) } +} - impl Encode for Vec4 { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(&mut w)?; - self.w.encode(&mut w) - } +impl Encode for IVec3 { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(w) } +} - impl Decode<'_> for Vec4 { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self::new( - f32::decode(r)?, - f32::decode(r)?, - f32::decode(r)?, - f32::decode(r)?, - )) - } +impl Decode<'_> for IVec3 { + fn decode(r: &mut &[u8]) -> anyhow::Result { + Ok(Self { + x: i32::decode(r)?, + y: i32::decode(r)?, + z: i32::decode(r)?, + }) } +} - impl Encode for Quat { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(&mut w)?; - self.w.encode(w) - } +impl Encode for Vec4 { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(&mut w)?; + self.w.encode(&mut w) } +} - impl Decode<'_> for Quat { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self::from_xyzw( - f32::decode(r)?, - f32::decode(r)?, - f32::decode(r)?, - f32::decode(r)?, - )) - } +impl Decode<'_> for Vec4 { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self::new( + f32::decode(r)?, + f32::decode(r)?, + f32::decode(r)?, + f32::decode(r)?, + )) } +} - impl Encode for DVec2 { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(w) - } +impl Encode for Quat { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(&mut w)?; + self.w.encode(w) } +} - impl Decode<'_> for DVec2 { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self { - x: f64::decode(r)?, - y: f64::decode(r)?, - }) - } +impl Decode<'_> for Quat { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self::from_xyzw( + f32::decode(r)?, + f32::decode(r)?, + f32::decode(r)?, + f32::decode(r)?, + )) } +} - impl Encode for DVec3 { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(w) - } +impl Encode for DVec2 { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(w) } +} - impl Decode<'_> for DVec3 { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self { - x: f64::decode(r)?, - y: f64::decode(r)?, - z: f64::decode(r)?, - }) - } +impl Decode<'_> for DVec2 { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self { + x: f64::decode(r)?, + y: f64::decode(r)?, + }) } +} - impl Encode for DQuat { - fn encode(&self, mut w: impl Write) -> Result<()> { - self.x.encode(&mut w)?; - self.y.encode(&mut w)?; - self.z.encode(&mut w)?; - self.w.encode(w) - } +impl Encode for DVec3 { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(w) } +} - impl Decode<'_> for DQuat { - fn decode(r: &mut &[u8]) -> Result { - Ok(Self::from_xyzw( - f64::decode(r)?, - f64::decode(r)?, - f64::decode(r)?, - f64::decode(r)?, - )) - } +impl Decode<'_> for DVec3 { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self { + x: f64::decode(r)?, + y: f64::decode(r)?, + z: f64::decode(r)?, + }) + } +} + +impl Encode for DQuat { + fn encode(&self, mut w: impl Write) -> Result<()> { + self.x.encode(&mut w)?; + self.y.encode(&mut w)?; + self.z.encode(&mut w)?; + self.w.encode(w) + } +} + +impl Decode<'_> for DQuat { + fn decode(r: &mut &[u8]) -> Result { + Ok(Self::from_xyzw( + f64::decode(r)?, + f64::decode(r)?, + f64::decode(r)?, + f64::decode(r)?, + )) } } @@ -450,18 +460,11 @@ 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. +/// Like tuples, fixed-length arrays are encoded and decoded without a VarInt +/// length prefix. impl Encode for [T; N] { - fn encode(&self, mut w: impl Write) -> Result<()> { - if T::HAS_ENCODE_SLICE { - return T::encode_slice(self, w); - } - - for t in self { - t.encode(&mut w)?; - } - - Ok(()) + fn encode(&self, w: impl Write) -> Result<()> { + T::encode_slice(self, w) } } @@ -517,15 +520,7 @@ impl Encode for [T] { VarInt(len as i32).encode(&mut w)?; - if T::HAS_ENCODE_SLICE { - return T::encode_slice(self, w); - } - - for t in self { - t.encode(&mut w)?; - } - - Ok(()) + T::encode_slice(self, w) } } diff --git a/crates/valence_core/src/packet/message_signature.rs b/crates/valence_core/src/packet/message_signature.rs new file mode 100644 index 0000000..bc78225 --- /dev/null +++ b/crates/valence_core/src/packet/message_signature.rs @@ -0,0 +1,40 @@ +use std::io::Write; + +use super::var_int::VarInt; +use super::{Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct MessageSignature<'a> { + pub message_id: i32, + pub signature: Option<&'a [u8; 256]>, +} + +impl<'a> Encode for MessageSignature<'a> { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { + VarInt(self.message_id + 1).encode(&mut w)?; + + match self.signature { + None => {} + Some(signature) => signature.encode(&mut w)?, + } + + Ok(()) + } +} + +impl<'a> Decode<'a> for MessageSignature<'a> { + fn decode(r: &mut &'a [u8]) -> anyhow::Result { + let message_id = VarInt::decode(r)?.0 - 1; // TODO: this can underflow. + + let signature = if message_id == -1 { + Some(<&[u8; 256]>::decode(r)?) + } else { + None + }; + + Ok(Self { + message_id, + signature, + }) + } +} diff --git a/crates/valence_protocol/src/raw.rs b/crates/valence_core/src/packet/raw.rs similarity index 81% rename from crates/valence_protocol/src/raw.rs rename to crates/valence_core/src/packet/raw.rs index a21faf7..93fc001 100644 --- a/crates/valence_protocol/src/raw.rs +++ b/crates/valence_core/src/packet/raw.rs @@ -1,7 +1,7 @@ use std::io::Write; use std::mem; -use crate::{Decode, Encode, Packet, Result}; +use crate::packet::{Decode, Encode, Packet}; /// While [encoding], the contained slice is written directly to the output /// without any length prefix or metadata. @@ -15,13 +15,13 @@ use crate::{Decode, Encode, Packet, Result}; pub struct RawBytes<'a>(pub &'a [u8]); impl Encode for RawBytes<'_> { - fn encode(&self, mut w: impl Write) -> Result<()> { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { Ok(w.write_all(self.0)?) } } impl<'a> Decode<'a> for RawBytes<'a> { - fn decode(r: &mut &'a [u8]) -> Result { + fn decode(r: &mut &'a [u8]) -> anyhow::Result { Ok(Self(mem::take(r))) } } @@ -52,11 +52,11 @@ impl<'a> Packet<'a> for RawPacket<'a> { "RawPacket" } - fn encode_packet(&self, mut w: impl Write) -> Result<()> { + fn encode_packet(&self, mut w: impl Write) -> anyhow::Result<()> { Ok(w.write_all(self.0)?) } - fn decode_packet(r: &mut &'a [u8]) -> Result { + fn decode_packet(r: &mut &'a [u8]) -> anyhow::Result { Ok(Self(mem::take(r))) } } diff --git a/crates/valence_protocol/src/packet/s2c.rs b/crates/valence_core/src/packet/s2c.rs similarity index 100% rename from crates/valence_protocol/src/packet/s2c.rs rename to crates/valence_core/src/packet/s2c.rs diff --git a/crates/valence_protocol/src/packet/s2c/login/login_compression.rs b/crates/valence_core/src/packet/s2c/login/login_compression.rs similarity index 59% rename from crates/valence_protocol/src/packet/s2c/login/login_compression.rs rename to crates/valence_core/src/packet/s2c/login/login_compression.rs index 28de385..d94dc92 100644 --- a/crates/valence_protocol/src/packet/s2c/login/login_compression.rs +++ b/crates/valence_core/src/packet/s2c/login/login_compression.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct LoginCompressionS2c { diff --git a/crates/valence_protocol/src/packet/s2c/login/login_disconnect.rs b/crates/valence_core/src/packet/s2c/login/login_disconnect.rs similarity index 80% rename from crates/valence_protocol/src/packet/s2c/login/login_disconnect.rs rename to crates/valence_core/src/packet/s2c/login/login_disconnect.rs index ec8392e..531e3ea 100644 --- a/crates/valence_protocol/src/packet/s2c/login/login_disconnect.rs +++ b/crates/valence_core/src/packet/s2c/login/login_disconnect.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct LoginDisconnectS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/login/login_hello.rs b/crates/valence_core/src/packet/s2c/login/login_hello.rs similarity index 82% rename from crates/valence_protocol/src/packet/s2c/login/login_hello.rs rename to crates/valence_core/src/packet/s2c/login/login_hello.rs index 89fa917..8be5ee5 100644 --- a/crates/valence_protocol/src/packet/s2c/login/login_hello.rs +++ b/crates/valence_core/src/packet/s2c/login/login_hello.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct LoginHelloS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/login/login_query_request.rs b/crates/valence_core/src/packet/s2c/login/login_query_request.rs similarity index 67% rename from crates/valence_protocol/src/packet/s2c/login/login_query_request.rs rename to crates/valence_core/src/packet/s2c/login/login_query_request.rs index d1e49ab..83da7ec 100644 --- a/crates/valence_protocol/src/packet/s2c/login/login_query_request.rs +++ b/crates/valence_core/src/packet/s2c/login/login_query_request.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::raw::RawBytes; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::raw::RawBytes; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct LoginQueryRequestS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/login/login_success.rs b/crates/valence_core/src/packet/s2c/login/login_success.rs similarity index 76% rename from crates/valence_protocol/src/packet/s2c/login/login_success.rs rename to crates/valence_core/src/packet/s2c/login/login_success.rs index 9a82165..8478270 100644 --- a/crates/valence_protocol/src/packet/s2c/login/login_success.rs +++ b/crates/valence_core/src/packet/s2c/login/login_success.rs @@ -2,8 +2,8 @@ use std::borrow::Cow; use uuid::Uuid; -use crate::types::Property; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; +use crate::property::Property; #[derive(Clone, Debug, Encode, Decode)] pub struct LoginSuccessS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/advancement_update.rs b/crates/valence_core/src/packet/s2c/play/advancement_update.rs similarity index 97% rename from crates/valence_protocol/src/packet/s2c/play/advancement_update.rs rename to crates/valence_core/src/packet/s2c/play/advancement_update.rs index 629795a..91bcd4e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/advancement_update.rs +++ b/crates/valence_core/src/packet/s2c/play/advancement_update.rs @@ -3,9 +3,9 @@ use std::io::Write; use crate::ident::Ident; use crate::item::ItemStack; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct AdvancementUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/block_breaking_progress.rs b/crates/valence_core/src/packet/s2c/play/block_breaking_progress.rs similarity index 73% rename from crates/valence_protocol/src/packet/s2c/play/block_breaking_progress.rs rename to crates/valence_core/src/packet/s2c/play/block_breaking_progress.rs index 0f7a79d..e07e0f1 100644 --- a/crates/valence_protocol/src/packet/s2c/play/block_breaking_progress.rs +++ b/crates/valence_core/src/packet/s2c/play/block_breaking_progress.rs @@ -1,6 +1,6 @@ use crate::block_pos::BlockPos; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct BlockBreakingProgressS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/block_entity_update.rs b/crates/valence_core/src/packet/s2c/play/block_entity_update.rs similarity index 70% rename from crates/valence_protocol/src/packet/s2c/play/block_entity_update.rs rename to crates/valence_core/src/packet/s2c/play/block_entity_update.rs index e7b9712..93920ec 100644 --- a/crates/valence_protocol/src/packet/s2c/play/block_entity_update.rs +++ b/crates/valence_core/src/packet/s2c/play/block_entity_update.rs @@ -2,13 +2,13 @@ use std::borrow::Cow; use valence_nbt::Compound; -use crate::block::BlockEntityKind; use crate::block_pos::BlockPos; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct BlockEntityUpdateS2c<'a> { pub position: BlockPos, - pub kind: BlockEntityKind, + pub kind: VarInt, pub data: Cow<'a, Compound>, } diff --git a/crates/valence_protocol/src/packet/s2c/play/block_event.rs b/crates/valence_core/src/packet/s2c/play/block_event.rs similarity index 74% rename from crates/valence_protocol/src/packet/s2c/play/block_event.rs rename to crates/valence_core/src/packet/s2c/play/block_event.rs index b15297c..9e47f15 100644 --- a/crates/valence_protocol/src/packet/s2c/play/block_event.rs +++ b/crates/valence_core/src/packet/s2c/play/block_event.rs @@ -1,6 +1,6 @@ use crate::block_pos::BlockPos; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct BlockEventS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/block_update.rs b/crates/valence_core/src/packet/s2c/play/block_update.rs similarity index 69% rename from crates/valence_protocol/src/packet/s2c/play/block_update.rs rename to crates/valence_core/src/packet/s2c/play/block_update.rs index 9d26486..b6f3ad6 100644 --- a/crates/valence_protocol/src/packet/s2c/play/block_update.rs +++ b/crates/valence_core/src/packet/s2c/play/block_update.rs @@ -1,6 +1,6 @@ use crate::block_pos::BlockPos; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct BlockUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/boss_bar.rs b/crates/valence_core/src/packet/s2c/play/boss_bar.rs similarity index 96% rename from crates/valence_protocol/src/packet/s2c/play/boss_bar.rs rename to crates/valence_core/src/packet/s2c/play/boss_bar.rs index f6b4580..05557c8 100644 --- a/crates/valence_protocol/src/packet/s2c/play/boss_bar.rs +++ b/crates/valence_core/src/packet/s2c/play/boss_bar.rs @@ -1,8 +1,8 @@ use bitfield_struct::bitfield; use uuid::Uuid; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct BossBarS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/bundle_splitter.rs b/crates/valence_core/src/packet/s2c/play/bundle_splitter.rs similarity index 66% rename from crates/valence_protocol/src/packet/s2c/play/bundle_splitter.rs rename to crates/valence_core/src/packet/s2c/play/bundle_splitter.rs index f7cb3e6..fb6d007 100644 --- a/crates/valence_protocol/src/packet/s2c/play/bundle_splitter.rs +++ b/crates/valence_core/src/packet/s2c/play/bundle_splitter.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct BundleSplitter; diff --git a/crates/valence_protocol/src/packet/s2c/play/chat_message.rs b/crates/valence_core/src/packet/s2c/play/chat_message.rs similarity index 95% rename from crates/valence_protocol/src/packet/s2c/play/chat_message.rs rename to crates/valence_core/src/packet/s2c/play/chat_message.rs index 65f3f88..c51ef50 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chat_message.rs +++ b/crates/valence_core/src/packet/s2c/play/chat_message.rs @@ -3,10 +3,10 @@ use std::io::Write; use uuid::Uuid; +use crate::packet::message_signature::MessageSignature; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::types::MessageSignature; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, PartialEq, Debug)] pub struct ChatMessageS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/chat_suggestions.rs b/crates/valence_core/src/packet/s2c/play/chat_suggestions.rs similarity index 88% rename from crates/valence_protocol/src/packet/s2c/play/chat_suggestions.rs rename to crates/valence_core/src/packet/s2c/play/chat_suggestions.rs index 995b66e..97005b1 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chat_suggestions.rs +++ b/crates/valence_core/src/packet/s2c/play/chat_suggestions.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ChatSuggestionsS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/chunk_biome_data.rs b/crates/valence_core/src/packet/s2c/play/chunk_biome_data.rs similarity index 77% rename from crates/valence_protocol/src/packet/s2c/play/chunk_biome_data.rs rename to crates/valence_core/src/packet/s2c/play/chunk_biome_data.rs index 8e06e98..1eeb32a 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chunk_biome_data.rs +++ b/crates/valence_core/src/packet/s2c/play/chunk_biome_data.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use crate::{Decode, Encode}; +use crate::chunk_pos::ChunkPos; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ChunkBiomeDataS2c<'a> { @@ -9,8 +10,7 @@ pub struct ChunkBiomeDataS2c<'a> { #[derive(Clone, Debug, Encode, Decode)] pub struct ChunkBiome<'a> { - pub chunk_x: i32, - pub chunk_z: i32, + pub pos: ChunkPos, /// Chunk data structure, with sections containing only the `Biomes` field. pub data: &'a [u8], } diff --git a/crates/valence_protocol/src/packet/s2c/play/chunk_data.rs b/crates/valence_core/src/packet/s2c/play/chunk_data.rs similarity index 79% rename from crates/valence_protocol/src/packet/s2c/play/chunk_data.rs rename to crates/valence_core/src/packet/s2c/play/chunk_data.rs index 19c225b..bb873ee 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chunk_data.rs +++ b/crates/valence_core/src/packet/s2c/play/chunk_data.rs @@ -2,14 +2,14 @@ use std::borrow::Cow; use valence_nbt::Compound; -use crate::array::LengthPrefixedArray; -use crate::block::BlockEntityKind; -use crate::{Decode, Encode}; +use crate::chunk_pos::ChunkPos; +use crate::packet::array::LengthPrefixedArray; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ChunkDataS2c<'a> { - pub chunk_x: i32, - pub chunk_z: i32, + pub pos: ChunkPos, pub heightmaps: Cow<'a, Compound>, pub blocks_and_biomes: &'a [u8], pub block_entities: Cow<'a, [ChunkDataBlockEntity<'a>]>, @@ -26,6 +26,6 @@ pub struct ChunkDataS2c<'a> { pub struct ChunkDataBlockEntity<'a> { pub packed_xz: i8, pub y: i16, - pub kind: BlockEntityKind, + pub kind: VarInt, pub data: Cow<'a, Compound>, } diff --git a/crates/valence_protocol/src/packet/s2c/play/chunk_delta_update.rs b/crates/valence_core/src/packet/s2c/play/chunk_delta_update.rs similarity index 73% rename from crates/valence_protocol/src/packet/s2c/play/chunk_delta_update.rs rename to crates/valence_core/src/packet/s2c/play/chunk_delta_update.rs index 71b9d49..779dc48 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chunk_delta_update.rs +++ b/crates/valence_core/src/packet/s2c/play/chunk_delta_update.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -use crate::var_long::VarLong; -use crate::{Decode, Encode}; +use crate::packet::var_long::VarLong; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ChunkDeltaUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/chunk_load_distance.rs b/crates/valence_core/src/packet/s2c/play/chunk_load_distance.rs similarity index 60% rename from crates/valence_protocol/src/packet/s2c/play/chunk_load_distance.rs rename to crates/valence_core/src/packet/s2c/play/chunk_load_distance.rs index 03fc76b..33100e5 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chunk_load_distance.rs +++ b/crates/valence_core/src/packet/s2c/play/chunk_load_distance.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ChunkLoadDistanceS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/chunk_render_distance_center.rs b/crates/valence_core/src/packet/s2c/play/chunk_render_distance_center.rs similarity index 65% rename from crates/valence_protocol/src/packet/s2c/play/chunk_render_distance_center.rs rename to crates/valence_core/src/packet/s2c/play/chunk_render_distance_center.rs index fd56d48..9e5c5fa 100644 --- a/crates/valence_protocol/src/packet/s2c/play/chunk_render_distance_center.rs +++ b/crates/valence_core/src/packet/s2c/play/chunk_render_distance_center.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ChunkRenderDistanceCenterS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/clear_title.rs b/crates/valence_core/src/packet/s2c/play/clear_title.rs similarity index 72% rename from crates/valence_protocol/src/packet/s2c/play/clear_title.rs rename to crates/valence_core/src/packet/s2c/play/clear_title.rs index 5476a6f..2eaeaba 100644 --- a/crates/valence_protocol/src/packet/s2c/play/clear_title.rs +++ b/crates/valence_core/src/packet/s2c/play/clear_title.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ClearTitleS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/close_screen.rs b/crates/valence_core/src/packet/s2c/play/close_screen.rs similarity index 78% rename from crates/valence_protocol/src/packet/s2c/play/close_screen.rs rename to crates/valence_core/src/packet/s2c/play/close_screen.rs index 0d96454..2a1a416 100644 --- a/crates/valence_protocol/src/packet/s2c/play/close_screen.rs +++ b/crates/valence_core/src/packet/s2c/play/close_screen.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct CloseScreenS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/command_suggestions.rs b/crates/valence_core/src/packet/s2c/play/command_suggestions.rs similarity index 83% rename from crates/valence_protocol/src/packet/s2c/play/command_suggestions.rs rename to crates/valence_core/src/packet/s2c/play/command_suggestions.rs index e2b1257..e33db3d 100644 --- a/crates/valence_protocol/src/packet/s2c/play/command_suggestions.rs +++ b/crates/valence_core/src/packet/s2c/play/command_suggestions.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CommandSuggestionsS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/command_tree.rs b/crates/valence_core/src/packet/s2c/play/command_tree.rs similarity index 99% rename from crates/valence_protocol/src/packet/s2c/play/command_tree.rs rename to crates/valence_core/src/packet/s2c/play/command_tree.rs index 9bd0588..3d74ebc 100644 --- a/crates/valence_protocol/src/packet/s2c/play/command_tree.rs +++ b/crates/valence_core/src/packet/s2c/play/command_tree.rs @@ -5,8 +5,8 @@ use anyhow::bail; use byteorder::WriteBytesExt; use crate::ident::Ident; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CommandTreeS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/cooldown_update.rs b/crates/valence_core/src/packet/s2c/play/cooldown_update.rs similarity index 65% rename from crates/valence_protocol/src/packet/s2c/play/cooldown_update.rs rename to crates/valence_core/src/packet/s2c/play/cooldown_update.rs index 92ddc6e..d6d78e8 100644 --- a/crates/valence_protocol/src/packet/s2c/play/cooldown_update.rs +++ b/crates/valence_core/src/packet/s2c/play/cooldown_update.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct CooldownUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/craft_failed_response.rs b/crates/valence_core/src/packet/s2c/play/craft_failed_response.rs similarity index 83% rename from crates/valence_protocol/src/packet/s2c/play/craft_failed_response.rs rename to crates/valence_core/src/packet/s2c/play/craft_failed_response.rs index 383781f..5e4af75 100644 --- a/crates/valence_protocol/src/packet/s2c/play/craft_failed_response.rs +++ b/crates/valence_core/src/packet/s2c/play/craft_failed_response.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CraftFailedResponseS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/custom_payload.rs b/crates/valence_core/src/packet/s2c/play/custom_payload.rs similarity index 72% rename from crates/valence_protocol/src/packet/s2c/play/custom_payload.rs rename to crates/valence_core/src/packet/s2c/play/custom_payload.rs index 3f4dda9..7dfdc90 100644 --- a/crates/valence_protocol/src/packet/s2c/play/custom_payload.rs +++ b/crates/valence_core/src/packet/s2c/play/custom_payload.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::raw::RawBytes; -use crate::{Decode, Encode}; +use crate::packet::raw::RawBytes; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct CustomPayloadS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/damage_tilt.rs b/crates/valence_core/src/packet/s2c/play/damage_tilt.rs similarity index 76% rename from crates/valence_protocol/src/packet/s2c/play/damage_tilt.rs rename to crates/valence_core/src/packet/s2c/play/damage_tilt.rs index 8016b14..7305305 100644 --- a/crates/valence_protocol/src/packet/s2c/play/damage_tilt.rs +++ b/crates/valence_core/src/packet/s2c/play/damage_tilt.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct DamageTiltS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/death_message.rs b/crates/valence_core/src/packet/s2c/play/death_message.rs similarity index 77% rename from crates/valence_protocol/src/packet/s2c/play/death_message.rs rename to crates/valence_core/src/packet/s2c/play/death_message.rs index bf8f708..fdf2333 100644 --- a/crates/valence_protocol/src/packet/s2c/play/death_message.rs +++ b/crates/valence_core/src/packet/s2c/play/death_message.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct DeathMessageS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/difficulty.rs b/crates/valence_core/src/packet/s2c/play/difficulty.rs similarity index 64% rename from crates/valence_protocol/src/packet/s2c/play/difficulty.rs rename to crates/valence_core/src/packet/s2c/play/difficulty.rs index 997a479..ada64a0 100644 --- a/crates/valence_protocol/src/packet/s2c/play/difficulty.rs +++ b/crates/valence_core/src/packet/s2c/play/difficulty.rs @@ -1,5 +1,5 @@ -use crate::types::Difficulty; -use crate::{Decode, Encode}; +use crate::difficulty::Difficulty; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct DifficultyS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/disconnect.rs b/crates/valence_core/src/packet/s2c/play/disconnect.rs similarity index 80% rename from crates/valence_protocol/src/packet/s2c/play/disconnect.rs rename to crates/valence_core/src/packet/s2c/play/disconnect.rs index 7b4852f..bd30e6c 100644 --- a/crates/valence_protocol/src/packet/s2c/play/disconnect.rs +++ b/crates/valence_core/src/packet/s2c/play/disconnect.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct DisconnectS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/end_combat.rs b/crates/valence_core/src/packet/s2c/play/end_combat.rs similarity index 69% rename from crates/valence_protocol/src/packet/s2c/play/end_combat.rs rename to crates/valence_core/src/packet/s2c/play/end_combat.rs index 67aadbb..035907a 100644 --- a/crates/valence_protocol/src/packet/s2c/play/end_combat.rs +++ b/crates/valence_core/src/packet/s2c/play/end_combat.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; /// Unused by notchian clients. #[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)] diff --git a/crates/valence_protocol/src/packet/s2c/play/enter_combat.rs b/crates/valence_core/src/packet/s2c/play/enter_combat.rs similarity index 75% rename from crates/valence_protocol/src/packet/s2c/play/enter_combat.rs rename to crates/valence_core/src/packet/s2c/play/enter_combat.rs index db9fac2..1753355 100644 --- a/crates/valence_protocol/src/packet/s2c/play/enter_combat.rs +++ b/crates/valence_core/src/packet/s2c/play/enter_combat.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; /// Unused by notchian clients. #[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)] diff --git a/crates/valence_protocol/src/packet/s2c/play/entities_destroy.rs b/crates/valence_core/src/packet/s2c/play/entities_destroy.rs similarity index 67% rename from crates/valence_protocol/src/packet/s2c/play/entities_destroy.rs rename to crates/valence_core/src/packet/s2c/play/entities_destroy.rs index c5d23e3..0fce612 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entities_destroy.rs +++ b/crates/valence_core/src/packet/s2c/play/entities_destroy.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct EntitiesDestroyS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_animation.rs b/crates/valence_core/src/packet/s2c/play/entity_animation.rs similarity index 68% rename from crates/valence_protocol/src/packet/s2c/play/entity_animation.rs rename to crates/valence_core/src/packet/s2c/play/entity_animation.rs index ba30643..dcec06d 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_animation.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_animation.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityAnimationS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_attach.rs b/crates/valence_core/src/packet/s2c/play/entity_attach.rs similarity index 79% rename from crates/valence_protocol/src/packet/s2c/play/entity_attach.rs rename to crates/valence_core/src/packet/s2c/play/entity_attach.rs index ba24d26..c3a9679 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_attach.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_attach.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityAttachS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_attributes.rs b/crates/valence_core/src/packet/s2c/play/entity_attributes.rs similarity index 88% rename from crates/valence_protocol/src/packet/s2c/play/entity_attributes.rs rename to crates/valence_core/src/packet/s2c/play/entity_attributes.rs index 428b91a..0cad84e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_attributes.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_attributes.rs @@ -3,8 +3,8 @@ use std::borrow::Cow; use uuid::Uuid; use crate::ident::Ident; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct EntityAttributesS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_damage.rs b/crates/valence_core/src/packet/s2c/play/entity_damage.rs similarity index 88% rename from crates/valence_protocol/src/packet/s2c/play/entity_damage.rs rename to crates/valence_core/src/packet/s2c/play/entity_damage.rs index 16692fe..5440398 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_damage.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_damage.rs @@ -1,5 +1,7 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityDamageS2c { @@ -19,5 +21,5 @@ pub struct EntityDamageS2c { pub source_direct_id: VarInt, /// The Notchian server sends the Source Position when the damage was dealt /// by the /damage command and a position was specified - pub source_pos: Option<[f64; 3]>, + pub source_pos: Option, } diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_equipment_update.rs b/crates/valence_core/src/packet/s2c/play/entity_equipment_update.rs similarity index 95% rename from crates/valence_protocol/src/packet/s2c/play/entity_equipment_update.rs rename to crates/valence_core/src/packet/s2c/play/entity_equipment_update.rs index 0f0886a..4473d12 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_equipment_update.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_equipment_update.rs @@ -1,8 +1,8 @@ use std::io::Write; use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, PartialEq, Debug)] pub struct EntityEquipmentUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_move.rs b/crates/valence_core/src/packet/s2c/play/entity_move.rs similarity index 82% rename from crates/valence_protocol/src/packet/s2c/play/entity_move.rs rename to crates/valence_core/src/packet/s2c/play/entity_move.rs index 9c6adba..9777d38 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_move.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_move.rs @@ -1,6 +1,6 @@ -use crate::byte_angle::ByteAngle; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::byte_angle::ByteAngle; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct MoveRelative { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_passengers_set.rs b/crates/valence_core/src/packet/s2c/play/entity_passengers_set.rs similarity index 69% rename from crates/valence_protocol/src/packet/s2c/play/entity_passengers_set.rs rename to crates/valence_core/src/packet/s2c/play/entity_passengers_set.rs index 4b69cf3..f0f2157 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_passengers_set.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_passengers_set.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct EntityPassengersSetS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_position.rs b/crates/valence_core/src/packet/s2c/play/entity_position.rs similarity index 53% rename from crates/valence_protocol/src/packet/s2c/play/entity_position.rs rename to crates/valence_core/src/packet/s2c/play/entity_position.rs index e9af827..8024c69 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_position.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_position.rs @@ -1,11 +1,13 @@ -use crate::byte_angle::ByteAngle; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::byte_angle::ByteAngle; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityPositionS2c { pub entity_id: VarInt, - pub position: [f64; 3], + pub position: DVec3, pub yaw: ByteAngle, pub pitch: ByteAngle, pub on_ground: bool, diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_set_head_yaw.rs b/crates/valence_core/src/packet/s2c/play/entity_set_head_yaw.rs similarity index 54% rename from crates/valence_protocol/src/packet/s2c/play/entity_set_head_yaw.rs rename to crates/valence_core/src/packet/s2c/play/entity_set_head_yaw.rs index 80df0de..dc7174b 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_set_head_yaw.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_set_head_yaw.rs @@ -1,6 +1,6 @@ -use crate::byte_angle::ByteAngle; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::byte_angle::ByteAngle; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntitySetHeadYawS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_spawn.rs b/crates/valence_core/src/packet/s2c/play/entity_spawn.rs similarity index 65% rename from crates/valence_protocol/src/packet/s2c/play/entity_spawn.rs rename to crates/valence_core/src/packet/s2c/play/entity_spawn.rs index 2381d92..b8b5fef 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_spawn.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_spawn.rs @@ -1,16 +1,16 @@ +use glam::DVec3; use uuid::Uuid; -use crate::byte_angle::ByteAngle; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::byte_angle::ByteAngle; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntitySpawnS2c { pub entity_id: VarInt, pub object_uuid: Uuid, - // TODO: EntityKind type? pub kind: VarInt, - pub position: [f64; 3], + pub position: DVec3, pub pitch: ByteAngle, pub yaw: ByteAngle, pub head_yaw: ByteAngle, diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_status.rs b/crates/valence_core/src/packet/s2c/play/entity_status.rs similarity index 77% rename from crates/valence_protocol/src/packet/s2c/play/entity_status.rs rename to crates/valence_core/src/packet/s2c/play/entity_status.rs index 6998b71..c876b1a 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_status.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_status.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityStatusS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_status_effect.rs b/crates/valence_core/src/packet/s2c/play/entity_status_effect.rs similarity index 87% rename from crates/valence_protocol/src/packet/s2c/play/entity_status_effect.rs rename to crates/valence_core/src/packet/s2c/play/entity_status_effect.rs index 3ac9a5b..aca8a81 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_status_effect.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_status_effect.rs @@ -1,8 +1,8 @@ use bitfield_struct::bitfield; use valence_nbt::Compound; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct EntityStatusEffectS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_tracker_update.rs b/crates/valence_core/src/packet/s2c/play/entity_tracker_update.rs similarity index 58% rename from crates/valence_protocol/src/packet/s2c/play/entity_tracker_update.rs rename to crates/valence_core/src/packet/s2c/play/entity_tracker_update.rs index e4f15a8..55536b0 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_tracker_update.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_tracker_update.rs @@ -1,6 +1,6 @@ -use crate::raw::RawBytes; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::raw::RawBytes; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityTrackerUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/entity_velocity_update.rs b/crates/valence_core/src/packet/s2c/play/entity_velocity_update.rs similarity index 65% rename from crates/valence_protocol/src/packet/s2c/play/entity_velocity_update.rs rename to crates/valence_core/src/packet/s2c/play/entity_velocity_update.rs index 5dc1353..e4010ee 100644 --- a/crates/valence_protocol/src/packet/s2c/play/entity_velocity_update.rs +++ b/crates/valence_core/src/packet/s2c/play/entity_velocity_update.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct EntityVelocityUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/experience_bar_update.rs b/crates/valence_core/src/packet/s2c/play/experience_bar_update.rs similarity index 67% rename from crates/valence_protocol/src/packet/s2c/play/experience_bar_update.rs rename to crates/valence_core/src/packet/s2c/play/experience_bar_update.rs index 881d8e2..92657b8 100644 --- a/crates/valence_protocol/src/packet/s2c/play/experience_bar_update.rs +++ b/crates/valence_core/src/packet/s2c/play/experience_bar_update.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ExperienceBarUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/experience_orb_spawn.rs b/crates/valence_core/src/packet/s2c/play/experience_orb_spawn.rs similarity index 53% rename from crates/valence_protocol/src/packet/s2c/play/experience_orb_spawn.rs rename to crates/valence_core/src/packet/s2c/play/experience_orb_spawn.rs index 5edb94e..c249bf0 100644 --- a/crates/valence_protocol/src/packet/s2c/play/experience_orb_spawn.rs +++ b/crates/valence_core/src/packet/s2c/play/experience_orb_spawn.rs @@ -1,9 +1,11 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ExperienceOrbSpawnS2c { pub entity_id: VarInt, - pub position: [f64; 3], + pub position: DVec3, pub count: i16, } diff --git a/crates/valence_protocol/src/packet/s2c/play/explosion.rs b/crates/valence_core/src/packet/s2c/play/explosion.rs similarity index 84% rename from crates/valence_protocol/src/packet/s2c/play/explosion.rs rename to crates/valence_core/src/packet/s2c/play/explosion.rs index 7230b95..8392444 100644 --- a/crates/valence_protocol/src/packet/s2c/play/explosion.rs +++ b/crates/valence_core/src/packet/s2c/play/explosion.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ExplosionS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/features.rs b/crates/valence_core/src/packet/s2c/play/features.rs similarity index 81% rename from crates/valence_protocol/src/packet/s2c/play/features.rs rename to crates/valence_core/src/packet/s2c/play/features.rs index e135e33..10c1249 100644 --- a/crates/valence_protocol/src/packet/s2c/play/features.rs +++ b/crates/valence_core/src/packet/s2c/play/features.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct FeaturesS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/game_join.rs b/crates/valence_core/src/packet/s2c/play/game_join.rs similarity index 84% rename from crates/valence_protocol/src/packet/s2c/play/game_join.rs rename to crates/valence_core/src/packet/s2c/play/game_join.rs index b7f853c..91e1ce3 100644 --- a/crates/valence_protocol/src/packet/s2c/play/game_join.rs +++ b/crates/valence_core/src/packet/s2c/play/game_join.rs @@ -2,10 +2,11 @@ use std::borrow::Cow; use valence_nbt::Compound; +use crate::game_mode::GameMode; use crate::ident::Ident; -use crate::types::{GameMode, GlobalPos}; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::global_pos::GlobalPos; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct GameJoinS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/game_message.rs b/crates/valence_core/src/packet/s2c/play/game_message.rs similarity index 86% rename from crates/valence_protocol/src/packet/s2c/play/game_message.rs rename to crates/valence_core/src/packet/s2c/play/game_message.rs index 94e30e8..d6f8283 100644 --- a/crates/valence_protocol/src/packet/s2c/play/game_message.rs +++ b/crates/valence_core/src/packet/s2c/play/game_message.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct GameMessageS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/game_state_change.rs b/crates/valence_core/src/packet/s2c/play/game_state_change.rs similarity index 92% rename from crates/valence_protocol/src/packet/s2c/play/game_state_change.rs rename to crates/valence_core/src/packet/s2c/play/game_state_change.rs index 7cd0063..2c90e79 100644 --- a/crates/valence_protocol/src/packet/s2c/play/game_state_change.rs +++ b/crates/valence_core/src/packet/s2c/play/game_state_change.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct GameStateChangeS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/health_update.rs b/crates/valence_core/src/packet/s2c/play/health_update.rs similarity index 67% rename from crates/valence_protocol/src/packet/s2c/play/health_update.rs rename to crates/valence_core/src/packet/s2c/play/health_update.rs index f5f06bf..71a37a4 100644 --- a/crates/valence_protocol/src/packet/s2c/play/health_update.rs +++ b/crates/valence_core/src/packet/s2c/play/health_update.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct HealthUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/inventory.rs b/crates/valence_core/src/packet/s2c/play/inventory.rs similarity index 78% rename from crates/valence_protocol/src/packet/s2c/play/inventory.rs rename to crates/valence_core/src/packet/s2c/play/inventory.rs index 0a16397..c2c516e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/inventory.rs +++ b/crates/valence_core/src/packet/s2c/play/inventory.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct InventoryS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/item_pickup_animation.rs b/crates/valence_core/src/packet/s2c/play/item_pickup_animation.rs similarity index 72% rename from crates/valence_protocol/src/packet/s2c/play/item_pickup_animation.rs rename to crates/valence_core/src/packet/s2c/play/item_pickup_animation.rs index 78f679a..f81e096 100644 --- a/crates/valence_protocol/src/packet/s2c/play/item_pickup_animation.rs +++ b/crates/valence_core/src/packet/s2c/play/item_pickup_animation.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ItemPickupAnimationS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/keep_alive.rs b/crates/valence_core/src/packet/s2c/play/keep_alive.rs similarity index 71% rename from crates/valence_protocol/src/packet/s2c/play/keep_alive.rs rename to crates/valence_core/src/packet/s2c/play/keep_alive.rs index 8373939..7968ef5 100644 --- a/crates/valence_protocol/src/packet/s2c/play/keep_alive.rs +++ b/crates/valence_core/src/packet/s2c/play/keep_alive.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct KeepAliveS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/light_update.rs b/crates/valence_core/src/packet/s2c/play/light_update.rs similarity index 78% rename from crates/valence_protocol/src/packet/s2c/play/light_update.rs rename to crates/valence_core/src/packet/s2c/play/light_update.rs index df299ea..b071098 100644 --- a/crates/valence_protocol/src/packet/s2c/play/light_update.rs +++ b/crates/valence_core/src/packet/s2c/play/light_update.rs @@ -1,6 +1,6 @@ -use crate::array::LengthPrefixedArray; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::array::LengthPrefixedArray; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct LightUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/look_at.rs b/crates/valence_core/src/packet/s2c/play/look_at.rs similarity index 77% rename from crates/valence_protocol/src/packet/s2c/play/look_at.rs rename to crates/valence_core/src/packet/s2c/play/look_at.rs index e0fd234..e1589d8 100644 --- a/crates/valence_protocol/src/packet/s2c/play/look_at.rs +++ b/crates/valence_core/src/packet/s2c/play/look_at.rs @@ -1,10 +1,12 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)] pub struct LookAtS2c { pub feet_or_eyes: FeetOrEyes, - pub target_position: [f64; 3], + pub target_position: DVec3, pub entity_to_face: Option, } diff --git a/crates/valence_protocol/src/packet/s2c/play/map_update.rs b/crates/valence_core/src/packet/s2c/play/map_update.rs similarity index 97% rename from crates/valence_protocol/src/packet/s2c/play/map_update.rs rename to crates/valence_core/src/packet/s2c/play/map_update.rs index b09362c..381983b 100644 --- a/crates/valence_protocol/src/packet/s2c/play/map_update.rs +++ b/crates/valence_core/src/packet/s2c/play/map_update.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use std::io::Write; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, PartialEq, Debug)] pub struct MapUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/nbt_query_response.rs b/crates/valence_core/src/packet/s2c/play/nbt_query_response.rs similarity index 68% rename from crates/valence_protocol/src/packet/s2c/play/nbt_query_response.rs rename to crates/valence_core/src/packet/s2c/play/nbt_query_response.rs index 392cb32..884b7b2 100644 --- a/crates/valence_protocol/src/packet/s2c/play/nbt_query_response.rs +++ b/crates/valence_core/src/packet/s2c/play/nbt_query_response.rs @@ -1,7 +1,7 @@ use valence_nbt::Compound; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct NbtQueryResponseS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/open_horse_screen.rs b/crates/valence_core/src/packet/s2c/play/open_horse_screen.rs similarity index 68% rename from crates/valence_protocol/src/packet/s2c/play/open_horse_screen.rs rename to crates/valence_core/src/packet/s2c/play/open_horse_screen.rs index bc1c8be..858a5f9 100644 --- a/crates/valence_protocol/src/packet/s2c/play/open_horse_screen.rs +++ b/crates/valence_core/src/packet/s2c/play/open_horse_screen.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct OpenHorseScreenS2c { diff --git a/crates/valence_core/src/packet/s2c/play/open_screen.rs b/crates/valence_core/src/packet/s2c/play/open_screen.rs new file mode 100644 index 0000000..5fdabd9 --- /dev/null +++ b/crates/valence_core/src/packet/s2c/play/open_screen.rs @@ -0,0 +1,40 @@ +use std::borrow::Cow; + +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; +use crate::text::Text; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub enum WindowType { + Generic9x1, + Generic9x2, + Generic9x3, + Generic9x4, + Generic9x5, + Generic9x6, + Generic3x3, + Anvil, + Beacon, + BlastFurnace, + BrewingStand, + Crafting, + Enchantment, + Furnace, + Grindstone, + Hopper, + Lectern, + Loom, + Merchant, + ShulkerBox, + Smithing, + Smoker, + Cartography, + Stonecutter, +} + +#[derive(Clone, Debug, Encode, Decode)] +pub struct OpenScreenS2c<'a> { + pub window_id: VarInt, + pub window_type: WindowType, + pub window_title: Cow<'a, Text>, +} diff --git a/crates/valence_protocol/src/packet/s2c/play/open_written_book.rs b/crates/valence_core/src/packet/s2c/play/open_written_book.rs similarity index 62% rename from crates/valence_protocol/src/packet/s2c/play/open_written_book.rs rename to crates/valence_core/src/packet/s2c/play/open_written_book.rs index 7cc88d1..ea643b8 100644 --- a/crates/valence_protocol/src/packet/s2c/play/open_written_book.rs +++ b/crates/valence_core/src/packet/s2c/play/open_written_book.rs @@ -1,5 +1,5 @@ -use crate::types::Hand; -use crate::{Decode, Encode}; +use crate::hand::Hand; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct OpenWrittenBookS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/overlay_message.rs b/crates/valence_core/src/packet/s2c/play/overlay_message.rs similarity index 81% rename from crates/valence_protocol/src/packet/s2c/play/overlay_message.rs rename to crates/valence_core/src/packet/s2c/play/overlay_message.rs index 63dc23f..ae69926 100644 --- a/crates/valence_protocol/src/packet/s2c/play/overlay_message.rs +++ b/crates/valence_core/src/packet/s2c/play/overlay_message.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct OverlayMessageS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/particle.rs b/crates/valence_core/src/packet/s2c/play/particle.rs similarity index 92% rename from crates/valence_protocol/src/packet/s2c/play/particle.rs rename to crates/valence_core/src/packet/s2c/play/particle.rs index 08dbd72..245393e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/particle.rs +++ b/crates/valence_core/src/packet/s2c/play/particle.rs @@ -2,19 +2,19 @@ use std::borrow::Cow; use std::io::Write; use anyhow::bail; +use glam::{DVec3, Vec3}; -use crate::block::BlockState; use crate::block_pos::BlockPos; use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug)] pub struct ParticleS2c<'a> { pub particle: Cow<'a, Particle>, pub long_distance: bool, - pub position: [f64; 3], - pub offset: [f32; 3], + pub position: DVec3, + pub offset: Vec3, pub max_speed: f32, pub count: i32, } @@ -23,8 +23,8 @@ pub struct ParticleS2c<'a> { pub enum Particle { AmbientEntityEffect, AngryVillager, - Block(BlockState), - BlockMarker(BlockState), + Block(i32), // TODO: use BlockState type. + BlockMarker(i32), // TODO: use BlockState type. Bubble, Cloud, Crit, @@ -36,13 +36,13 @@ pub enum Particle { DrippingWater, FallingWater, Dust { - rgb: [f32; 3], + rgb: Vec3, scale: f32, }, DustColorTransition { - from_rgb: [f32; 3], + from_rgb: Vec3, scale: f32, - to_rgb: [f32; 3], + to_rgb: Vec3, }, Effect, ElderGuardian, @@ -53,7 +53,7 @@ pub enum Particle { ExplosionEmitter, Explosion, SonicBoom, - FallingDust(BlockState), + FallingDust(i32), // TODO: use BlockState type. Firework, Fishing, Flame, @@ -248,8 +248,8 @@ impl Particle { Ok(match particle_id { 0 => Particle::AmbientEntityEffect, 1 => Particle::AngryVillager, - 2 => Particle::Block(BlockState::decode(r)?), - 3 => Particle::BlockMarker(BlockState::decode(r)?), + 2 => Particle::Block(VarInt::decode(r)?.0), + 3 => Particle::BlockMarker(VarInt::decode(r)?.0), 4 => Particle::Bubble, 5 => Particle::Cloud, 6 => Particle::Crit, @@ -261,13 +261,13 @@ impl Particle { 12 => Particle::DrippingWater, 13 => Particle::FallingWater, 14 => Particle::Dust { - rgb: <[f32; 3]>::decode(r)?, - scale: f32::decode(r)?, + rgb: Decode::decode(r)?, + scale: Decode::decode(r)?, }, 15 => Particle::DustColorTransition { - from_rgb: <[f32; 3]>::decode(r)?, - scale: f32::decode(r)?, - to_rgb: <[f32; 3]>::decode(r)?, + from_rgb: Decode::decode(r)?, + scale: Decode::decode(r)?, + to_rgb: Decode::decode(r)?, }, 16 => Particle::Effect, 17 => Particle::ElderGuardian, @@ -278,7 +278,7 @@ impl Particle { 22 => Particle::ExplosionEmitter, 23 => Particle::Explosion, 24 => Particle::SonicBoom, - 25 => Particle::FallingDust(BlockState::decode(r)?), + 25 => Particle::FallingDust(VarInt::decode(r)?.0), 26 => Particle::Firework, 27 => Particle::Fishing, 28 => Particle::Flame, @@ -386,8 +386,8 @@ impl<'a> Decode<'a> for ParticleS2c<'a> { fn decode(r: &mut &'a [u8]) -> anyhow::Result { let particle_id = VarInt::decode(r)?.0; let long_distance = bool::decode(r)?; - let position = <[f64; 3]>::decode(r)?; - let offset = <[f32; 3]>::decode(r)?; + let position = Decode::decode(r)?; + let offset = Decode::decode(r)?; let max_speed = f32::decode(r)?; let particle_count = i32::decode(r)?; @@ -406,8 +406,8 @@ impl<'a> Decode<'a> for ParticleS2c<'a> { impl Encode for Particle { fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { match self { - Particle::Block(block_state) => block_state.encode(w), - Particle::BlockMarker(block_state) => block_state.encode(w), + Particle::Block(block_state) => VarInt(*block_state).encode(w), + Particle::BlockMarker(block_state) => VarInt(*block_state).encode(w), Particle::Dust { rgb, scale } => { rgb.encode(&mut w)?; scale.encode(w) @@ -421,7 +421,7 @@ impl Encode for Particle { scale.encode(&mut w)?; to_rgb.encode(w) } - Particle::FallingDust(block_state) => block_state.encode(w), + Particle::FallingDust(block_state) => VarInt(*block_state).encode(w), Particle::SculkCharge { roll } => roll.encode(w), Particle::Item(stack) => stack.encode(w), Particle::VibrationBlock { block_pos, ticks } => { diff --git a/crates/valence_protocol/src/packet/s2c/play/play_ping.rs b/crates/valence_core/src/packet/s2c/play/play_ping.rs similarity index 71% rename from crates/valence_protocol/src/packet/s2c/play/play_ping.rs rename to crates/valence_core/src/packet/s2c/play/play_ping.rs index 7cc34a1..d541fec 100644 --- a/crates/valence_protocol/src/packet/s2c/play/play_ping.rs +++ b/crates/valence_core/src/packet/s2c/play/play_ping.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayPingS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/play_sound.rs b/crates/valence_core/src/packet/s2c/play/play_sound.rs similarity index 89% rename from crates/valence_protocol/src/packet/s2c/play/play_sound.rs rename to crates/valence_core/src/packet/s2c/play/play_sound.rs index c34880e..c0ddcba 100644 --- a/crates/valence_protocol/src/packet/s2c/play/play_sound.rs +++ b/crates/valence_core/src/packet/s2c/play/play_sound.rs @@ -1,16 +1,18 @@ use std::borrow::Cow; use std::io::Write; +use glam::IVec3; + use crate::ident::Ident; -use crate::types::SoundCategory; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; +use crate::sound::SoundCategory; #[derive(Clone, Debug, Encode, Decode)] pub struct PlaySoundS2c<'a> { pub id: SoundId<'a>, pub category: SoundCategory, - pub position: [i32; 3], + pub position: IVec3, pub volume: f32, pub pitch: f32, pub seed: i64, diff --git a/crates/valence_protocol/src/packet/s2c/play/play_sound_from_entity.rs b/crates/valence_core/src/packet/s2c/play/play_sound_from_entity.rs similarity index 67% rename from crates/valence_protocol/src/packet/s2c/play/play_sound_from_entity.rs rename to crates/valence_core/src/packet/s2c/play/play_sound_from_entity.rs index 76c9ec8..b74c442 100644 --- a/crates/valence_protocol/src/packet/s2c/play/play_sound_from_entity.rs +++ b/crates/valence_core/src/packet/s2c/play/play_sound_from_entity.rs @@ -1,6 +1,6 @@ -use crate::types::SoundCategory; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; +use crate::sound::SoundCategory; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlaySoundFromEntityS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_abilities.rs b/crates/valence_core/src/packet/s2c/play/player_abilities.rs similarity index 92% rename from crates/valence_protocol/src/packet/s2c/play/player_abilities.rs rename to crates/valence_core/src/packet/s2c/play/player_abilities.rs index 7f61966..a850383 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_abilities.rs +++ b/crates/valence_core/src/packet/s2c/play/player_abilities.rs @@ -1,6 +1,6 @@ use bitfield_struct::bitfield; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct PlayerAbilitiesS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_action_response.rs b/crates/valence_core/src/packet/s2c/play/player_action_response.rs similarity index 60% rename from crates/valence_protocol/src/packet/s2c/play/player_action_response.rs rename to crates/valence_core/src/packet/s2c/play/player_action_response.rs index abce7db..ada9039 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_action_response.rs +++ b/crates/valence_core/src/packet/s2c/play/player_action_response.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerActionResponseS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_list.rs b/crates/valence_core/src/packet/s2c/play/player_list.rs similarity index 96% rename from crates/valence_protocol/src/packet/s2c/play/player_list.rs rename to crates/valence_core/src/packet/s2c/play/player_list.rs index 0af77b4..fef3bc8 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_list.rs +++ b/crates/valence_core/src/packet/s2c/play/player_list.rs @@ -4,10 +4,11 @@ use std::io::Write; use bitfield_struct::bitfield; use uuid::Uuid; +use crate::game_mode::GameMode; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; +use crate::property::Property; use crate::text::Text; -use crate::types::{GameMode, Property}; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, Debug)] pub struct PlayerListS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_list_header.rs b/crates/valence_core/src/packet/s2c/play/player_list_header.rs similarity index 83% rename from crates/valence_protocol/src/packet/s2c/play/player_list_header.rs rename to crates/valence_core/src/packet/s2c/play/player_list_header.rs index 9220c7b..6b24f07 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_list_header.rs +++ b/crates/valence_core/src/packet/s2c/play/player_list_header.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct PlayerListHeaderS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_position_look.rs b/crates/valence_core/src/packet/s2c/play/player_position_look.rs similarity index 78% rename from crates/valence_protocol/src/packet/s2c/play/player_position_look.rs rename to crates/valence_core/src/packet/s2c/play/player_position_look.rs index dab446f..15156e3 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_position_look.rs +++ b/crates/valence_core/src/packet/s2c/play/player_position_look.rs @@ -1,11 +1,12 @@ use bitfield_struct::bitfield; +use glam::DVec3; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)] pub struct PlayerPositionLookS2c { - pub position: [f64; 3], + pub position: DVec3, pub yaw: f32, pub pitch: f32, pub flags: Flags, diff --git a/crates/valence_protocol/src/packet/s2c/play/player_remove.rs b/crates/valence_core/src/packet/s2c/play/player_remove.rs similarity index 81% rename from crates/valence_protocol/src/packet/s2c/play/player_remove.rs rename to crates/valence_core/src/packet/s2c/play/player_remove.rs index e28fcff..c868473 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_remove.rs +++ b/crates/valence_core/src/packet/s2c/play/player_remove.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use uuid::Uuid; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct PlayerRemoveS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_respawn.rs b/crates/valence_core/src/packet/s2c/play/player_respawn.rs similarity index 80% rename from crates/valence_protocol/src/packet/s2c/play/player_respawn.rs rename to crates/valence_core/src/packet/s2c/play/player_respawn.rs index 0232ccf..fde8d87 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_respawn.rs +++ b/crates/valence_core/src/packet/s2c/play/player_respawn.rs @@ -1,8 +1,9 @@ use std::borrow::Cow; +use crate::game_mode::GameMode; use crate::ident::Ident; -use crate::types::{GameMode, GlobalPos}; -use crate::{Decode, Encode}; +use crate::packet::global_pos::GlobalPos; +use crate::packet::{Decode, Encode}; #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct PlayerRespawnS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/player_spawn.rs b/crates/valence_core/src/packet/s2c/play/player_spawn.rs similarity index 55% rename from crates/valence_protocol/src/packet/s2c/play/player_spawn.rs rename to crates/valence_core/src/packet/s2c/play/player_spawn.rs index 2fb249c..e878858 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_spawn.rs +++ b/crates/valence_core/src/packet/s2c/play/player_spawn.rs @@ -1,14 +1,15 @@ +use glam::DVec3; use uuid::Uuid; -use crate::byte_angle::ByteAngle; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::byte_angle::ByteAngle; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerSpawnS2c { pub entity_id: VarInt, pub player_uuid: Uuid, - pub position: [f64; 3], + pub position: DVec3, pub yaw: ByteAngle, pub pitch: ByteAngle, } diff --git a/crates/valence_protocol/src/packet/s2c/play/player_spawn_position.rs b/crates/valence_core/src/packet/s2c/play/player_spawn_position.rs similarity index 81% rename from crates/valence_protocol/src/packet/s2c/play/player_spawn_position.rs rename to crates/valence_core/src/packet/s2c/play/player_spawn_position.rs index 4a649a0..e84c50d 100644 --- a/crates/valence_protocol/src/packet/s2c/play/player_spawn_position.rs +++ b/crates/valence_core/src/packet/s2c/play/player_spawn_position.rs @@ -1,5 +1,5 @@ use crate::block_pos::BlockPos; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct PlayerSpawnPositionS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/profileless_chat_message.rs b/crates/valence_core/src/packet/s2c/play/profileless_chat_message.rs similarity index 78% rename from crates/valence_protocol/src/packet/s2c/play/profileless_chat_message.rs rename to crates/valence_core/src/packet/s2c/play/profileless_chat_message.rs index 575763a..641b6e7 100644 --- a/crates/valence_protocol/src/packet/s2c/play/profileless_chat_message.rs +++ b/crates/valence_core/src/packet/s2c/play/profileless_chat_message.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ProfilelessChatMessageS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/remove_entity_status_effect.rs b/crates/valence_core/src/packet/s2c/play/remove_entity_status_effect.rs similarity index 67% rename from crates/valence_protocol/src/packet/s2c/play/remove_entity_status_effect.rs rename to crates/valence_core/src/packet/s2c/play/remove_entity_status_effect.rs index d6ea421..202a768 100644 --- a/crates/valence_protocol/src/packet/s2c/play/remove_entity_status_effect.rs +++ b/crates/valence_core/src/packet/s2c/play/remove_entity_status_effect.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct RemoveEntityStatusEffectS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/remove_message.rs b/crates/valence_core/src/packet/s2c/play/remove_message.rs similarity index 57% rename from crates/valence_protocol/src/packet/s2c/play/remove_message.rs rename to crates/valence_core/src/packet/s2c/play/remove_message.rs index d80f921..17cbf63 100644 --- a/crates/valence_protocol/src/packet/s2c/play/remove_message.rs +++ b/crates/valence_core/src/packet/s2c/play/remove_message.rs @@ -1,5 +1,5 @@ -use crate::types::MessageSignature; -use crate::{Decode, Encode}; +use crate::packet::message_signature::MessageSignature; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct RemoveMessageS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/resource_pack_send.rs b/crates/valence_core/src/packet/s2c/play/resource_pack_send.rs similarity index 87% rename from crates/valence_protocol/src/packet/s2c/play/resource_pack_send.rs rename to crates/valence_core/src/packet/s2c/play/resource_pack_send.rs index 2980361..1fc471e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/resource_pack_send.rs +++ b/crates/valence_core/src/packet/s2c/play/resource_pack_send.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct ResourcePackSendS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/scoreboard_display.rs b/crates/valence_core/src/packet/s2c/play/scoreboard_display.rs similarity index 99% rename from crates/valence_protocol/src/packet/s2c/play/scoreboard_display.rs rename to crates/valence_core/src/packet/s2c/play/scoreboard_display.rs index 41caa33..11e5436 100644 --- a/crates/valence_protocol/src/packet/s2c/play/scoreboard_display.rs +++ b/crates/valence_core/src/packet/s2c/play/scoreboard_display.rs @@ -1,5 +1,5 @@ use super::team::TeamColor; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ScoreboardDisplayS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/scoreboard_objective_update.rs b/crates/valence_core/src/packet/s2c/play/scoreboard_objective_update.rs similarity index 93% rename from crates/valence_protocol/src/packet/s2c/play/scoreboard_objective_update.rs rename to crates/valence_core/src/packet/s2c/play/scoreboard_objective_update.rs index 1c5e752..719601d 100644 --- a/crates/valence_protocol/src/packet/s2c/play/scoreboard_objective_update.rs +++ b/crates/valence_core/src/packet/s2c/play/scoreboard_objective_update.rs @@ -1,5 +1,5 @@ +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ScoreboardObjectiveUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/scoreboard_player_update.rs b/crates/valence_core/src/packet/s2c/play/scoreboard_player_update.rs similarity index 83% rename from crates/valence_protocol/src/packet/s2c/play/scoreboard_player_update.rs rename to crates/valence_core/src/packet/s2c/play/scoreboard_player_update.rs index 09211d5..ba4fa49 100644 --- a/crates/valence_protocol/src/packet/s2c/play/scoreboard_player_update.rs +++ b/crates/valence_core/src/packet/s2c/play/scoreboard_player_update.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ScoreboardPlayerUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/screen_handler_property_update.rs b/crates/valence_core/src/packet/s2c/play/screen_handler_property_update.rs similarity index 81% rename from crates/valence_protocol/src/packet/s2c/play/screen_handler_property_update.rs rename to crates/valence_core/src/packet/s2c/play/screen_handler_property_update.rs index 980d60d..68adf9b 100644 --- a/crates/valence_protocol/src/packet/s2c/play/screen_handler_property_update.rs +++ b/crates/valence_core/src/packet/s2c/play/screen_handler_property_update.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct ScreenHandlerPropertyUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/screen_handler_slot_update.rs b/crates/valence_core/src/packet/s2c/play/screen_handler_slot_update.rs similarity index 77% rename from crates/valence_protocol/src/packet/s2c/play/screen_handler_slot_update.rs rename to crates/valence_core/src/packet/s2c/play/screen_handler_slot_update.rs index 3bae895..3fc8ddd 100644 --- a/crates/valence_protocol/src/packet/s2c/play/screen_handler_slot_update.rs +++ b/crates/valence_core/src/packet/s2c/play/screen_handler_slot_update.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ScreenHandlerSlotUpdateS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/select_advancement_tab.rs b/crates/valence_core/src/packet/s2c/play/select_advancement_tab.rs similarity index 83% rename from crates/valence_protocol/src/packet/s2c/play/select_advancement_tab.rs rename to crates/valence_core/src/packet/s2c/play/select_advancement_tab.rs index 33db5e6..c7bc1f7 100644 --- a/crates/valence_protocol/src/packet/s2c/play/select_advancement_tab.rs +++ b/crates/valence_core/src/packet/s2c/play/select_advancement_tab.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SelectAdvancementTabS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/server_metadata.rs b/crates/valence_core/src/packet/s2c/play/server_metadata.rs similarity index 85% rename from crates/valence_protocol/src/packet/s2c/play/server_metadata.rs rename to crates/valence_core/src/packet/s2c/play/server_metadata.rs index 539fc95..3a6ea1d 100644 --- a/crates/valence_protocol/src/packet/s2c/play/server_metadata.rs +++ b/crates/valence_core/src/packet/s2c/play/server_metadata.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct ServerMetadataS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/set_camera_entity.rs b/crates/valence_core/src/packet/s2c/play/set_camera_entity.rs similarity index 58% rename from crates/valence_protocol/src/packet/s2c/play/set_camera_entity.rs rename to crates/valence_core/src/packet/s2c/play/set_camera_entity.rs index 370aab5..835a555 100644 --- a/crates/valence_protocol/src/packet/s2c/play/set_camera_entity.rs +++ b/crates/valence_core/src/packet/s2c/play/set_camera_entity.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SetCameraEntityS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/set_trade_offers.rs b/crates/valence_core/src/packet/s2c/play/set_trade_offers.rs similarity index 90% rename from crates/valence_protocol/src/packet/s2c/play/set_trade_offers.rs rename to crates/valence_core/src/packet/s2c/play/set_trade_offers.rs index 7ed2b3a..231e1c6 100644 --- a/crates/valence_protocol/src/packet/s2c/play/set_trade_offers.rs +++ b/crates/valence_core/src/packet/s2c/play/set_trade_offers.rs @@ -1,6 +1,6 @@ use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SetTradeOffersS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/sign_editor_open.rs b/crates/valence_core/src/packet/s2c/play/sign_editor_open.rs similarity index 79% rename from crates/valence_protocol/src/packet/s2c/play/sign_editor_open.rs rename to crates/valence_core/src/packet/s2c/play/sign_editor_open.rs index e873a7c..df1192e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/sign_editor_open.rs +++ b/crates/valence_core/src/packet/s2c/play/sign_editor_open.rs @@ -1,5 +1,5 @@ use crate::block_pos::BlockPos; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct SignEditorOpenS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/simulation_distance.rs b/crates/valence_core/src/packet/s2c/play/simulation_distance.rs similarity index 61% rename from crates/valence_protocol/src/packet/s2c/play/simulation_distance.rs rename to crates/valence_core/src/packet/s2c/play/simulation_distance.rs index b3d07da..3729976 100644 --- a/crates/valence_protocol/src/packet/s2c/play/simulation_distance.rs +++ b/crates/valence_core/src/packet/s2c/play/simulation_distance.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SimulationDistanceS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/statistics.rs b/crates/valence_core/src/packet/s2c/play/statistics.rs similarity index 78% rename from crates/valence_protocol/src/packet/s2c/play/statistics.rs rename to crates/valence_core/src/packet/s2c/play/statistics.rs index d57bafe..c026ad4 100644 --- a/crates/valence_protocol/src/packet/s2c/play/statistics.rs +++ b/crates/valence_core/src/packet/s2c/play/statistics.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct StatisticsS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/stop_sound.rs b/crates/valence_core/src/packet/s2c/play/stop_sound.rs similarity index 95% rename from crates/valence_protocol/src/packet/s2c/play/stop_sound.rs rename to crates/valence_core/src/packet/s2c/play/stop_sound.rs index 7494919..af6bf27 100644 --- a/crates/valence_protocol/src/packet/s2c/play/stop_sound.rs +++ b/crates/valence_core/src/packet/s2c/play/stop_sound.rs @@ -2,8 +2,8 @@ use std::borrow::Cow; use std::io::Write; use crate::ident::Ident; -use crate::types::SoundCategory; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; +use crate::sound::SoundCategory; #[derive(Clone, PartialEq, Debug)] pub struct StopSoundS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/subtitle.rs b/crates/valence_core/src/packet/s2c/play/subtitle.rs similarity index 80% rename from crates/valence_protocol/src/packet/s2c/play/subtitle.rs rename to crates/valence_core/src/packet/s2c/play/subtitle.rs index 8316504..575defa 100644 --- a/crates/valence_protocol/src/packet/s2c/play/subtitle.rs +++ b/crates/valence_core/src/packet/s2c/play/subtitle.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SubtitleS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/synchronize_recipes.rs b/crates/valence_core/src/packet/s2c/play/synchronize_recipes.rs similarity index 99% rename from crates/valence_protocol/src/packet/s2c/play/synchronize_recipes.rs rename to crates/valence_core/src/packet/s2c/play/synchronize_recipes.rs index a1c2355..cc3a38f 100644 --- a/crates/valence_protocol/src/packet/s2c/play/synchronize_recipes.rs +++ b/crates/valence_core/src/packet/s2c/play/synchronize_recipes.rs @@ -5,13 +5,13 @@ use anyhow::{bail, ensure}; use crate::ident::Ident; use crate::item::ItemStack; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SynchronizeRecipesS2c<'a> { // TODO: this should be a Vec> - pub recipes: crate::raw::RawBytes<'a>, + pub recipes: crate::packet::raw::RawBytes<'a>, } #[derive(Clone, PartialEq, Debug)] diff --git a/crates/valence_protocol/src/packet/s2c/play/synchronize_tags.rs b/crates/valence_core/src/packet/s2c/play/synchronize_tags.rs similarity index 86% rename from crates/valence_protocol/src/packet/s2c/play/synchronize_tags.rs rename to crates/valence_core/src/packet/s2c/play/synchronize_tags.rs index 55952c6..9502a8c 100644 --- a/crates/valence_protocol/src/packet/s2c/play/synchronize_tags.rs +++ b/crates/valence_core/src/packet/s2c/play/synchronize_tags.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use crate::ident::Ident; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct SynchronizeTagsS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/team.rs b/crates/valence_core/src/packet/s2c/play/team.rs similarity index 99% rename from crates/valence_protocol/src/packet/s2c/play/team.rs rename to crates/valence_core/src/packet/s2c/play/team.rs index fd3052f..189a44d 100644 --- a/crates/valence_protocol/src/packet/s2c/play/team.rs +++ b/crates/valence_core/src/packet/s2c/play/team.rs @@ -4,8 +4,8 @@ use std::io::Write; use anyhow::bail; use bitfield_struct::bitfield; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct TeamS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/title.rs b/crates/valence_core/src/packet/s2c/play/title.rs similarity index 80% rename from crates/valence_protocol/src/packet/s2c/play/title.rs rename to crates/valence_core/src/packet/s2c/play/title.rs index 794f27e..446214f 100644 --- a/crates/valence_protocol/src/packet/s2c/play/title.rs +++ b/crates/valence_core/src/packet/s2c/play/title.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; +use crate::packet::{Decode, Encode}; use crate::text::Text; -use crate::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct TitleS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/title_fade.rs b/crates/valence_core/src/packet/s2c/play/title_fade.rs similarity index 87% rename from crates/valence_protocol/src/packet/s2c/play/title_fade.rs rename to crates/valence_core/src/packet/s2c/play/title_fade.rs index 517d88c..1266795 100644 --- a/crates/valence_protocol/src/packet/s2c/play/title_fade.rs +++ b/crates/valence_core/src/packet/s2c/play/title_fade.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct TitleFadeS2c { diff --git a/crates/valence_core/src/packet/s2c/play/unload_chunk.rs b/crates/valence_core/src/packet/s2c/play/unload_chunk.rs new file mode 100644 index 0000000..c616709 --- /dev/null +++ b/crates/valence_core/src/packet/s2c/play/unload_chunk.rs @@ -0,0 +1,7 @@ +use crate::chunk_pos::ChunkPos; +use crate::packet::{Decode, Encode}; + +#[derive(Copy, Clone, Debug, Encode, Decode)] +pub struct UnloadChunkS2c { + pub pos: ChunkPos, +} diff --git a/crates/valence_protocol/src/packet/s2c/play/unlock_recipes.rs b/crates/valence_core/src/packet/s2c/play/unlock_recipes.rs similarity index 97% rename from crates/valence_protocol/src/packet/s2c/play/unlock_recipes.rs rename to crates/valence_core/src/packet/s2c/play/unlock_recipes.rs index e5307c1..77d6333 100644 --- a/crates/valence_protocol/src/packet/s2c/play/unlock_recipes.rs +++ b/crates/valence_core/src/packet/s2c/play/unlock_recipes.rs @@ -4,8 +4,8 @@ use std::io::Write; use anyhow::bail; use crate::ident::Ident; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, PartialEq, Eq, Debug)] pub struct UnlockRecipesS2c<'a> { diff --git a/crates/valence_protocol/src/packet/s2c/play/update_selected_slot.rs b/crates/valence_core/src/packet/s2c/play/update_selected_slot.rs similarity index 73% rename from crates/valence_protocol/src/packet/s2c/play/update_selected_slot.rs rename to crates/valence_core/src/packet/s2c/play/update_selected_slot.rs index a721de0..76d9f17 100644 --- a/crates/valence_protocol/src/packet/s2c/play/update_selected_slot.rs +++ b/crates/valence_core/src/packet/s2c/play/update_selected_slot.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct UpdateSelectedSlotS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/vehicle_move.rs b/crates/valence_core/src/packet/s2c/play/vehicle_move.rs similarity index 58% rename from crates/valence_protocol/src/packet/s2c/play/vehicle_move.rs rename to crates/valence_core/src/packet/s2c/play/vehicle_move.rs index fa65067..6e15f06 100644 --- a/crates/valence_protocol/src/packet/s2c/play/vehicle_move.rs +++ b/crates/valence_core/src/packet/s2c/play/vehicle_move.rs @@ -1,8 +1,10 @@ -use crate::{Decode, Encode}; +use glam::DVec3; + +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct VehicleMoveS2c { - pub position: [f64; 3], + pub position: DVec3, pub yaw: f32, pub pitch: f32, } diff --git a/crates/valence_protocol/src/packet/s2c/play/world_border_center_changed.rs b/crates/valence_core/src/packet/s2c/play/world_border_center_changed.rs similarity index 52% rename from crates/valence_protocol/src/packet/s2c/play/world_border_center_changed.rs rename to crates/valence_core/src/packet/s2c/play/world_border_center_changed.rs index b8852c1..5ea01ba 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_border_center_changed.rs +++ b/crates/valence_core/src/packet/s2c/play/world_border_center_changed.rs @@ -1,6 +1,7 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct WorldBorderCenterChangedS2c { - pub xz_position: [f64; 2], + pub x_pos: f64, + pub z_pos: f64, } diff --git a/crates/valence_protocol/src/packet/s2c/play/world_border_initialize.rs b/crates/valence_core/src/packet/s2c/play/world_border_initialize.rs similarity index 73% rename from crates/valence_protocol/src/packet/s2c/play/world_border_initialize.rs rename to crates/valence_core/src/packet/s2c/play/world_border_initialize.rs index 06413a5..ac515b4 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_border_initialize.rs +++ b/crates/valence_core/src/packet/s2c/play/world_border_initialize.rs @@ -1,6 +1,6 @@ -use crate::var_int::VarInt; -use crate::var_long::VarLong; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::var_long::VarLong; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct WorldBorderInitializeS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/world_border_interpolate_size.rs b/crates/valence_core/src/packet/s2c/play/world_border_interpolate_size.rs similarity index 68% rename from crates/valence_protocol/src/packet/s2c/play/world_border_interpolate_size.rs rename to crates/valence_core/src/packet/s2c/play/world_border_interpolate_size.rs index a4020b8..f0d07e3 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_border_interpolate_size.rs +++ b/crates/valence_core/src/packet/s2c/play/world_border_interpolate_size.rs @@ -1,5 +1,5 @@ -use crate::var_long::VarLong; -use crate::{Decode, Encode}; +use crate::packet::var_long::VarLong; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct WorldBorderInterpolateSizeS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/world_border_size_changed.rs b/crates/valence_core/src/packet/s2c/play/world_border_size_changed.rs similarity index 73% rename from crates/valence_protocol/src/packet/s2c/play/world_border_size_changed.rs rename to crates/valence_core/src/packet/s2c/play/world_border_size_changed.rs index bf3f626..e2903eb 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_border_size_changed.rs +++ b/crates/valence_core/src/packet/s2c/play/world_border_size_changed.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct WorldBorderSizeChangedS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/world_border_warning_blocks_changed.rs b/crates/valence_core/src/packet/s2c/play/world_border_warning_blocks_changed.rs similarity index 62% rename from crates/valence_protocol/src/packet/s2c/play/world_border_warning_blocks_changed.rs rename to crates/valence_core/src/packet/s2c/play/world_border_warning_blocks_changed.rs index cbbe53b..70433f2 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_border_warning_blocks_changed.rs +++ b/crates/valence_core/src/packet/s2c/play/world_border_warning_blocks_changed.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct WorldBorderWarningBlocksChangedS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/world_border_warning_time_changed.rs b/crates/valence_core/src/packet/s2c/play/world_border_warning_time_changed.rs similarity index 61% rename from crates/valence_protocol/src/packet/s2c/play/world_border_warning_time_changed.rs rename to crates/valence_core/src/packet/s2c/play/world_border_warning_time_changed.rs index 9d8a5d4..e1df494 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_border_warning_time_changed.rs +++ b/crates/valence_core/src/packet/s2c/play/world_border_warning_time_changed.rs @@ -1,5 +1,5 @@ -use crate::var_int::VarInt; -use crate::{Decode, Encode}; +use crate::packet::var_int::VarInt; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct WorldBorderWarningTimeChangedS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/world_event.rs b/crates/valence_core/src/packet/s2c/play/world_event.rs similarity index 84% rename from crates/valence_protocol/src/packet/s2c/play/world_event.rs rename to crates/valence_core/src/packet/s2c/play/world_event.rs index 2a1e087..23b7adf 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_event.rs +++ b/crates/valence_core/src/packet/s2c/play/world_event.rs @@ -1,5 +1,5 @@ use crate::block_pos::BlockPos; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Clone, Debug, Encode, Decode)] pub struct WorldEventS2c { diff --git a/crates/valence_protocol/src/packet/s2c/play/world_time_update.rs b/crates/valence_core/src/packet/s2c/play/world_time_update.rs similarity index 90% rename from crates/valence_protocol/src/packet/s2c/play/world_time_update.rs rename to crates/valence_core/src/packet/s2c/play/world_time_update.rs index 7b0bbcd..e74032e 100644 --- a/crates/valence_protocol/src/packet/s2c/play/world_time_update.rs +++ b/crates/valence_core/src/packet/s2c/play/world_time_update.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct WorldTimeUpdateS2c { diff --git a/crates/valence_protocol/src/packet/s2c/status/query_pong.rs b/crates/valence_core/src/packet/s2c/status/query_pong.rs similarity index 72% rename from crates/valence_protocol/src/packet/s2c/status/query_pong.rs rename to crates/valence_core/src/packet/s2c/status/query_pong.rs index 1c0058b..889a48b 100644 --- a/crates/valence_protocol/src/packet/s2c/status/query_pong.rs +++ b/crates/valence_core/src/packet/s2c/status/query_pong.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct QueryPongS2c { diff --git a/crates/valence_protocol/src/packet/s2c/status/query_response.rs b/crates/valence_core/src/packet/s2c/status/query_response.rs similarity index 74% rename from crates/valence_protocol/src/packet/s2c/status/query_response.rs rename to crates/valence_core/src/packet/s2c/status/query_response.rs index b448409..3bb4c0a 100644 --- a/crates/valence_protocol/src/packet/s2c/status/query_response.rs +++ b/crates/valence_core/src/packet/s2c/status/query_response.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; #[derive(Copy, Clone, Debug, Encode, Decode)] pub struct QueryResponseS2c<'a> { diff --git a/crates/valence_protocol/src/var_int.rs b/crates/valence_core/src/packet/var_int.rs similarity index 99% rename from crates/valence_protocol/src/var_int.rs rename to crates/valence_core/src/packet/var_int.rs index a60f886..52dcdf9 100644 --- a/crates/valence_protocol/src/var_int.rs +++ b/crates/valence_core/src/packet/var_int.rs @@ -4,7 +4,7 @@ use anyhow::bail; use byteorder::ReadBytesExt; use thiserror::Error; -use crate::{Decode, Encode}; +use crate::packet::{Decode, Encode}; /// An `i32` encoded with variable length. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] diff --git a/crates/valence_protocol/src/var_long.rs b/crates/valence_core/src/packet/var_long.rs similarity index 95% rename from crates/valence_protocol/src/var_long.rs rename to crates/valence_core/src/packet/var_long.rs index 9003935..13a831f 100644 --- a/crates/valence_protocol/src/var_long.rs +++ b/crates/valence_core/src/packet/var_long.rs @@ -3,7 +3,7 @@ use std::io::Write; use anyhow::bail; use byteorder::ReadBytesExt; -use crate::{Decode, Encode, Result}; +use crate::packet::{Decode, Encode}; /// An `i64` encoded with variable length. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -32,7 +32,7 @@ impl Encode for VarLong { any(target_arch = "x86", target_arch = "x86_64"), not(target_os = "macos") ))] - fn encode(&self, mut w: impl Write) -> Result<()> { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { #[cfg(target_arch = "x86")] use std::arch::x86::*; #[cfg(target_arch = "x86_64")] @@ -81,7 +81,7 @@ impl Encode for VarLong { not(any(target_arch = "x86", target_arch = "x86_64")), target_os = "macos" ))] - fn encode(&self, mut w: impl Write) -> Result<()> { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { use byteorder::WriteBytesExt; let mut val = self.0 as u64; @@ -97,7 +97,7 @@ impl Encode for VarLong { } impl Decode<'_> for VarLong { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { let mut val = 0; for i in 0..Self::MAX_SIZE { let byte = r.read_u8()?; diff --git a/crates/valence/src/player_textures.rs b/crates/valence_core/src/player_textures.rs similarity index 97% rename from crates/valence/src/player_textures.rs rename to crates/valence_core/src/player_textures.rs index 2c806df..31659a7 100644 --- a/crates/valence/src/player_textures.rs +++ b/crates/valence_core/src/player_textures.rs @@ -4,7 +4,8 @@ use anyhow::Context; use base64::prelude::*; use serde::Deserialize; use url::Url; -use valence_protocol::types::Property; + +use crate::property::Property; /// Contains URLs to the skin and cape of a player. #[derive(Clone, PartialEq, Eq, Debug)] diff --git a/crates/valence_core/src/property.rs b/crates/valence_core/src/property.rs new file mode 100644 index 0000000..96dfae9 --- /dev/null +++ b/crates/valence_core/src/property.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +use crate::packet::{Decode, Encode}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)] +pub struct Property { + pub name: S, + pub value: S, + pub signature: Option, +} diff --git a/crates/valence_core/src/scratch.rs b/crates/valence_core/src/scratch.rs new file mode 100644 index 0000000..6d48629 --- /dev/null +++ b/crates/valence_core/src/scratch.rs @@ -0,0 +1,8 @@ +use bevy_ecs::prelude::*; + +/// General-purpose reusable byte buffer. +/// +/// No guarantees are made about the buffer's contents between systems. +/// Therefore, the inner `Vec` should be cleared before use. +#[derive(Component, Default, Debug)] +pub struct ScratchBuf(pub Vec); diff --git a/crates/valence_protocol/src/sound.rs b/crates/valence_core/src/sound.rs similarity index 70% rename from crates/valence_protocol/src/sound.rs rename to crates/valence_core/src/sound.rs index 916c1f0..bc75452 100644 --- a/crates/valence_protocol/src/sound.rs +++ b/crates/valence_core/src/sound.rs @@ -1,6 +1,7 @@ use crate::ident; use crate::ident::Ident; use crate::packet::s2c::play::play_sound::SoundId; +use crate::packet::{Decode, Encode}; include!(concat!(env!("OUT_DIR"), "/sound.rs")); @@ -13,6 +14,20 @@ impl Sound { } } +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub enum SoundCategory { + Master, + Music, + Record, + Weather, + Block, + Hostile, + Neutral, + Player, + Ambient, + Voice, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/valence_protocol/src/text.rs b/crates/valence_core/src/text.rs similarity index 99% rename from crates/valence_protocol/src/text.rs rename to crates/valence_core/src/text.rs index 13846f1..b17b071 100644 --- a/crates/valence_protocol/src/text.rs +++ b/crates/valence_core/src/text.rs @@ -11,7 +11,7 @@ use uuid::Uuid; use valence_nbt::Value; use crate::ident::Ident; -use crate::{Decode, Encode, Result}; +use crate::packet::{Decode, Encode}; /// Represents formatted text in Minecraft's JSON text format. /// @@ -26,7 +26,7 @@ use crate::{Decode, Encode, Result}; /// /// With [`TextFormat`] in scope, you can write the following: /// ``` -/// use valence_protocol::text::{Color, Text, TextFormat}; +/// use valence_core::text::{Color, Text, TextFormat}; /// /// let txt = "The text is ".into_text() /// + "Red".color(Color::RED) @@ -853,13 +853,13 @@ impl fmt::Display for Text { } impl Encode for Text { - fn encode(&self, w: impl Write) -> Result<()> { + fn encode(&self, w: impl Write) -> anyhow::Result<()> { serde_json::to_string(self)?.encode(w) } } impl Decode<'_> for Text { - fn decode(r: &mut &[u8]) -> Result { + fn decode(r: &mut &[u8]) -> anyhow::Result { let string = <&str>::decode(r)?; if string.is_empty() { Ok(Self::default()) diff --git a/crates/valence_protocol/src/translation_key.rs b/crates/valence_core/src/translation_key.rs similarity index 100% rename from crates/valence_protocol/src/translation_key.rs rename to crates/valence_core/src/translation_key.rs diff --git a/crates/valence_core/src/util.rs b/crates/valence_core/src/util.rs new file mode 100644 index 0000000..349d428 --- /dev/null +++ b/crates/valence_core/src/util.rs @@ -0,0 +1,22 @@ +pub use glam::*; + +#[cfg(test)] +mod tests { + use approx::assert_relative_eq; + use rand::random; + + use super::*; + + #[test] + fn yaw_pitch_round_trip() { + for _ in 0..=100 { + let d = (Vec3::new(random(), random(), random()) * 2.0 - 1.0).normalize(); + + let (yaw, pitch) = to_yaw_and_pitch(d); + let d_new = from_yaw_and_pitch(yaw, pitch); + + assert_relative_eq!(d, d_new, epsilon = f32::EPSILON * 100.0); + } + } +} + diff --git a/crates/valence_core/src/uuid.rs b/crates/valence_core/src/uuid.rs new file mode 100644 index 0000000..ce3c3ff --- /dev/null +++ b/crates/valence_core/src/uuid.rs @@ -0,0 +1,17 @@ +use bevy_ecs::prelude::*; +use uuid::Uuid; + +/// The universally unique identifier of an entity. Component wrapper for a +/// [`Uuid`]. +/// +/// This component is expected to remain _unique_ and _constant_ during the +/// lifetime of the entity. The [`Default`] impl generates a new random UUID. +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] +pub struct UniqueId(pub Uuid); + +/// Generates a new random UUID. +impl Default for UniqueId { + fn default() -> Self { + Self(Uuid::from_bytes(rand::random())) + } +} diff --git a/crates/valence_core_macros/Cargo.toml b/crates/valence_core_macros/Cargo.toml new file mode 100644 index 0000000..4e7ef88 --- /dev/null +++ b/crates/valence_core_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "valence_core_macros" +version.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn = { workspace = true, features = ["full"] } diff --git a/crates/valence_core_macros/README.md b/crates/valence_core_macros/README.md new file mode 100644 index 0000000..aa4a880 --- /dev/null +++ b/crates/valence_core_macros/README.md @@ -0,0 +1,3 @@ +# valence_core_macros + +Procedural macros for `valence_core` diff --git a/crates/valence_protocol_macros/src/decode.rs b/crates/valence_core_macros/src/decode.rs similarity index 89% rename from crates/valence_protocol_macros/src/decode.rs rename to crates/valence_core_macros/src/decode.rs index 8c1b25e..a930ee2 100644 --- a/crates/valence_protocol_macros/src/decode.rs +++ b/crates/valence_core_macros/src/decode.rs @@ -5,7 +5,7 @@ use syn::{parse2, parse_quote, Data, DeriveInput, Error, Fields, Result}; use crate::{add_trait_bounds, decode_split_for_impl, pair_variants_with_discriminants}; -pub fn derive_decode(item: TokenStream) -> Result { +pub(super) fn derive_decode(item: TokenStream) -> Result { let mut input = parse2::(item)?; let input_name = input.ident; @@ -63,7 +63,7 @@ pub fn derive_decode(item: TokenStream) -> Result { add_trait_bounds( &mut input.generics, - quote!(::valence_protocol::Decode<#lifetime>), + quote!(::valence_core::__private::Decode<#lifetime>), ); let (impl_generics, ty_generics, where_clause) = @@ -71,11 +71,11 @@ pub fn derive_decode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports)] - impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #input_name #ty_generics + impl #impl_generics ::valence_core::__private::Decode<#lifetime> for #input_name #ty_generics #where_clause { - fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result { - use ::valence_protocol::__private::{Decode, Context, ensure}; + fn decode(_r: &mut &#lifetime [u8]) -> ::valence_core::__private::Result { + use ::valence_core::__private::{Decode, Context, ensure}; Ok(#decode_fields) } @@ -135,7 +135,7 @@ pub fn derive_decode(item: TokenStream) -> Result { add_trait_bounds( &mut input.generics, - quote!(::valence_protocol::Decode<#lifetime>), + quote!(::valence_core::__private::Decode<#lifetime>), ); let (impl_generics, ty_generics, where_clause) = @@ -143,11 +143,11 @@ pub fn derive_decode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports)] - impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #input_name #ty_generics + impl #impl_generics ::valence_core::__private::Decode<#lifetime> for #input_name #ty_generics #where_clause { - fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result { - use ::valence_protocol::__private::{Decode, Context, VarInt, bail}; + fn decode(_r: &mut &#lifetime [u8]) -> ::valence_core::__private::Result { + use ::valence_core::__private::{Decode, Context, VarInt, bail}; let ctx = concat!("failed to decode enum discriminant in `", stringify!(#input_name), "`"); let disc = VarInt::decode(_r).context(ctx)?.0; diff --git a/crates/valence_protocol_macros/src/encode.rs b/crates/valence_core_macros/src/encode.rs similarity index 91% rename from crates/valence_protocol_macros/src/encode.rs rename to crates/valence_core_macros/src/encode.rs index c2c22ad..cd2152b 100644 --- a/crates/valence_protocol_macros/src/encode.rs +++ b/crates/valence_core_macros/src/encode.rs @@ -5,14 +5,14 @@ use syn::{parse2, Data, DeriveInput, Error, Fields, LitInt, Result}; use crate::{add_trait_bounds, pair_variants_with_discriminants}; -pub fn derive_encode(item: TokenStream) -> Result { +pub(super) fn derive_encode(item: TokenStream) -> Result { let mut input = parse2::(item)?; let input_name = input.ident; add_trait_bounds( &mut input.generics, - quote!(::valence_protocol::__private::Encode), + quote!(::valence_core::__private::Encode), ); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -45,11 +45,11 @@ pub fn derive_encode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports)] - impl #impl_generics ::valence_protocol::__private::Encode for #input_name #ty_generics + impl #impl_generics ::valence_core::__private::Encode for #input_name #ty_generics #where_clause { - fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> { - use ::valence_protocol::__private::{Encode, Context}; + fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_core::__private::Result<()> { + use ::valence_core::__private::{Encode, Context}; #encode_fields @@ -143,11 +143,11 @@ pub fn derive_encode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports, unreachable_code)] - impl #impl_generics ::valence_protocol::Encode for #input_name #ty_generics + impl #impl_generics ::valence_core::__private::Encode for #input_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}; + fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_core::__private::Result<()> { + use ::valence_core::__private::{Encode, VarInt, Context}; match self { #encode_arms diff --git a/crates/valence_protocol_macros/src/ident.rs b/crates/valence_core_macros/src/ident.rs similarity index 86% rename from crates/valence_protocol_macros/src/ident.rs rename to crates/valence_core_macros/src/ident.rs index d6e422a..d5d34f7 100644 --- a/crates/valence_protocol_macros/src/ident.rs +++ b/crates/valence_core_macros/src/ident.rs @@ -14,7 +14,7 @@ fn check_path(s: &str) -> bool { .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/')) } -pub fn ident(item: TokenStream) -> Result { +pub(super) fn parse_ident_str(item: TokenStream) -> Result { let ident_lit: LitStr = parse2(item)?; let mut ident = ident_lit.value(); @@ -31,7 +31,5 @@ pub fn ident(item: TokenStream) -> Result { } } - Ok(quote! { - ::valence_protocol::ident::Ident::new_unchecked(#ident) - }) + Ok(quote!(#ident)) } diff --git a/crates/valence_protocol_macros/src/lib.rs b/crates/valence_core_macros/src/lib.rs similarity index 85% rename from crates/valence_protocol_macros/src/lib.rs rename to crates/valence_core_macros/src/lib.rs index 1b58093..803702f 100644 --- a/crates/valence_protocol_macros/src/lib.rs +++ b/crates/valence_core_macros/src/lib.rs @@ -1,8 +1,21 @@ -//! This crate provides derive macros for [`Encode`], [`Decode`], and -//! [`Packet`]. It also provides the procedural macro [`ident!`] for parsing -//! identifiers at compile time. -//! -//! See `valence_protocol`'s documentation for more information. +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] use proc_macro::TokenStream as StdTokenStream; use proc_macro2::TokenStream; @@ -42,8 +55,8 @@ pub fn derive_packet(item: StdTokenStream) -> StdTokenStream { } #[proc_macro] -pub fn ident(item: StdTokenStream) -> StdTokenStream { - match ident::ident(item.into()) { +pub fn parse_ident_str(item: StdTokenStream) -> StdTokenStream { + match ident::parse_ident_str(item.into()) { Ok(tokens) => tokens.into(), Err(e) => e.into_compile_error().into(), } diff --git a/crates/valence_protocol_macros/src/packet.rs b/crates/valence_core_macros/src/packet.rs similarity index 80% rename from crates/valence_protocol_macros/src/packet.rs rename to crates/valence_core_macros/src/packet.rs index 3ad3951..169d6c9 100644 --- a/crates/valence_protocol_macros/src/packet.rs +++ b/crates/valence_core_macros/src/packet.rs @@ -4,7 +4,7 @@ use syn::{parse2, parse_quote, Attribute, DeriveInput, Error, Lit, LitInt, Meta, use crate::{add_trait_bounds, decode_split_for_impl}; -pub fn derive_packet(item: TokenStream) -> Result { +pub(super) fn derive_packet(item: TokenStream) -> Result { let mut input = parse2::(item)?; let Some(packet_id) = find_packet_id_attr(&input.attrs)? else { @@ -23,12 +23,12 @@ pub fn derive_packet(item: TokenStream) -> Result { add_trait_bounds( &mut input.generics, - quote!(::valence_protocol::__private::Encode), + quote!(::valence_core::__private::Encode), ); add_trait_bounds( &mut input.generics, - quote!(::valence_protocol::__private::Decode<#lifetime>), + quote!(::valence_core::__private::Decode<#lifetime>), ); add_trait_bounds(&mut input.generics, quote!(::std::fmt::Debug)); @@ -40,7 +40,7 @@ pub fn derive_packet(item: TokenStream) -> Result { let name = input.ident; Ok(quote! { - impl #impl_generics ::valence_protocol::__private::Packet<#lifetime> for #name #ty_generics + impl #impl_generics ::valence_core::__private::Packet<#lifetime> for #name #ty_generics #where_clause { const PACKET_ID: i32 = #packet_id; @@ -53,8 +53,8 @@ pub fn derive_packet(item: TokenStream) -> Result { #name_str } - fn encode_packet(&self, mut w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> { - use ::valence_protocol::__private::{Encode, Context, VarInt}; + fn encode_packet(&self, mut w: impl ::std::io::Write) -> ::valence_core::__private::Result<()> { + use ::valence_core::__private::{Encode, Context, VarInt}; VarInt(#packet_id) .encode(&mut w) @@ -63,11 +63,11 @@ pub fn derive_packet(item: TokenStream) -> Result { Encode::encode(self, w) } - fn decode_packet(r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result { - use ::valence_protocol::__private::{Decode, Context, VarInt}; + fn decode_packet(r: &mut &#lifetime [u8]) -> ::valence_core::__private::Result { + use ::valence_core::__private::{Decode, Context, VarInt}; let id = VarInt::decode(r).context("failed to decode packet ID")?.0; - ::valence_protocol::__private::ensure!( + ::valence_core::__private::ensure!( id == #packet_id, "unexpected packet ID {} (expected {})", id, #packet_id ); diff --git a/crates/valence_dimension/Cargo.toml b/crates/valence_dimension/Cargo.toml new file mode 100644 index 0000000..b7e884c --- /dev/null +++ b/crates/valence_dimension/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "valence_dimension" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +tracing.workspace = true +valence_registry.workspace = true +valence_nbt.workspace = true +valence_core.workspace = true diff --git a/crates/valence_dimension/README.md b/crates/valence_dimension/README.md new file mode 100644 index 0000000..c89ea59 --- /dev/null +++ b/crates/valence_dimension/README.md @@ -0,0 +1,8 @@ +# valence_dimension + +Contains dimension types and the dimension type registry. Minecraft's default dimensions are added to the registry by default. + +### **NOTE:** +- Modifying the dimension type registry after the server has started can +break invariants within instances and clients! Make sure there are no +instances or clients spawned before mutating. diff --git a/crates/valence/src/dimension.rs b/crates/valence_dimension/src/lib.rs similarity index 91% rename from crates/valence/src/dimension.rs rename to crates/valence_dimension/src/lib.rs index 2f365a1..7ff57e8 100644 --- a/crates/valence/src/dimension.rs +++ b/crates/valence_dimension/src/lib.rs @@ -1,10 +1,21 @@ -//! Dimension type configuration and identification. -//! -//! **NOTE:** -//! -//! - Modifying the dimension type registry after the server has started can -//! break invariants within instances and clients! Make sure there are no -//! instances or clients spawned before mutating. +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] use std::collections::BTreeMap; use std::str::FromStr; @@ -13,122 +24,33 @@ use anyhow::{bail, Context}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; use tracing::{error, warn}; +use valence_core::ident; +use valence_core::ident::Ident; use valence_nbt::{compound, Value}; -use valence_protocol::ident; -use valence_protocol::ident::Ident; +use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue}; -use crate::registry_codec::{RegistryCodec, RegistryCodecSet, RegistryValue}; +pub struct DimensionPlugin; -#[derive(Resource)] -pub struct DimensionTypeRegistry { - name_to_dimension: BTreeMap, Entity>, -} - -impl DimensionTypeRegistry { - pub const KEY: Ident<&str> = ident!("minecraft:dimension_type"); - - pub fn get_by_name(&self, name: Ident<&str>) -> Option { - self.name_to_dimension.get(name.as_str()).copied() - } - - pub fn dimensions(&self) -> impl Iterator + '_ { - self.name_to_dimension.values().copied() - } -} - -#[derive(Component, Clone, PartialEq, Debug)] -pub struct DimensionType { - pub name: Ident, - pub ambient_light: f32, - pub bed_works: bool, - pub coordinate_scale: f64, - pub effects: DimensionEffects, - pub has_ceiling: bool, - pub has_raids: bool, - pub has_skylight: bool, - pub height: i32, - pub infiniburn: String, - pub logical_height: i32, - pub min_y: i32, - pub monster_spawn_block_light_limit: i32, - /// TODO: monster_spawn_light_level - pub natural: bool, - pub piglin_safe: bool, - pub respawn_anchor_works: bool, - pub ultrawarm: bool, -} - -impl Default for DimensionType { - fn default() -> Self { - Self { - name: ident!("minecraft:overworld").into(), - ambient_light: 1.0, - bed_works: true, - coordinate_scale: 1.0, - effects: DimensionEffects::default(), - has_ceiling: false, - has_raids: true, - has_skylight: true, - height: 384, - infiniburn: "#minecraft:infiniburn_overworld".into(), - logical_height: 384, - min_y: -64, - monster_spawn_block_light_limit: 0, - natural: true, - piglin_safe: false, - respawn_anchor_works: true, - ultrawarm: false, - } - } -} - -/// Determines what skybox/fog effects to use in dimensions. -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -pub enum DimensionEffects { - #[default] - Overworld, - TheNether, - TheEnd, -} - -impl From for Ident<&'static str> { - fn from(value: DimensionEffects) -> Self { - match value { - DimensionEffects::Overworld => ident!("overworld"), - DimensionEffects::TheNether => ident!("the_nether"), - DimensionEffects::TheEnd => ident!("the_end"), - } - } -} - -impl FromStr for DimensionEffects { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match Ident::new(s)?.as_str() { - "minecraft:overworld" => Ok(DimensionEffects::Overworld), - "minecraft:the_nether" => Ok(DimensionEffects::TheNether), - "minecraft:the_end" => Ok(DimensionEffects::TheEnd), - other => bail!("unknown dimension effect \"{other}\""), - } - } -} - -pub(crate) struct DimensionPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct DimensionSet; impl Plugin for DimensionPlugin { fn build(&self, app: &mut App) { app.insert_resource(DimensionTypeRegistry { name_to_dimension: BTreeMap::new(), }) + .configure_set( + DimensionSet + .in_base_set(CoreSet::PostUpdate) + .before(RegistryCodecSet), + ) .add_systems( ( update_dimension_type_registry, remove_dimension_types_from_registry, ) .chain() - .in_base_set(CoreSet::PostUpdate) - .before(RegistryCodecSet), + .in_set(DimensionSet), ) .add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup)); } @@ -253,3 +175,98 @@ fn load_default_dimension_types( error!("failed to load default dimension types from registry codec: {e:#}"); } } + +#[derive(Resource)] +pub struct DimensionTypeRegistry { + name_to_dimension: BTreeMap, Entity>, +} + +impl DimensionTypeRegistry { + pub const KEY: Ident<&str> = ident!("minecraft:dimension_type"); + + pub fn get_by_name(&self, name: Ident<&str>) -> Option { + self.name_to_dimension.get(name.as_str()).copied() + } + + pub fn dimensions(&self) -> impl Iterator + '_ { + self.name_to_dimension.values().copied() + } +} + +#[derive(Component, Clone, PartialEq, Debug)] +pub struct DimensionType { + pub name: Ident, + pub ambient_light: f32, + pub bed_works: bool, + pub coordinate_scale: f64, + pub effects: DimensionEffects, + pub has_ceiling: bool, + pub has_raids: bool, + pub has_skylight: bool, + pub height: i32, + pub infiniburn: String, + pub logical_height: i32, + pub min_y: i32, + pub monster_spawn_block_light_limit: i32, + /// TODO: monster_spawn_light_level + pub natural: bool, + pub piglin_safe: bool, + pub respawn_anchor_works: bool, + pub ultrawarm: bool, +} + +impl Default for DimensionType { + fn default() -> Self { + Self { + name: ident!("minecraft:overworld").into(), + ambient_light: 1.0, + bed_works: true, + coordinate_scale: 1.0, + effects: DimensionEffects::default(), + has_ceiling: false, + has_raids: true, + has_skylight: true, + height: 384, + infiniburn: "#minecraft:infiniburn_overworld".into(), + logical_height: 384, + min_y: -64, + monster_spawn_block_light_limit: 0, + natural: true, + piglin_safe: false, + respawn_anchor_works: true, + ultrawarm: false, + } + } +} + +/// Determines what skybox/fog effects to use in dimensions. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +pub enum DimensionEffects { + #[default] + Overworld, + TheNether, + TheEnd, +} + +impl From for Ident<&'static str> { + fn from(value: DimensionEffects) -> Self { + match value { + DimensionEffects::Overworld => ident!("overworld"), + DimensionEffects::TheNether => ident!("the_nether"), + DimensionEffects::TheEnd => ident!("the_end"), + } + } +} + +impl FromStr for DimensionEffects { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match Ident::new(s)?.as_str() { + "minecraft:overworld" => Ok(DimensionEffects::Overworld), + "minecraft:the_nether" => Ok(DimensionEffects::TheNether), + "minecraft:the_end" => Ok(DimensionEffects::TheEnd), + other => bail!("unknown dimension effect \"{other}\""), + } + } +} diff --git a/crates/valence_entity/Cargo.toml b/crates/valence_entity/Cargo.toml new file mode 100644 index 0000000..0d91012 --- /dev/null +++ b/crates/valence_entity/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "valence_entity" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +uuid.workspace = true +tracing.workspace = true +rustc-hash.workspace = true +paste.workspace = true +glam.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +valence_core.workspace = true +valence_block.workspace = true +valence_nbt.workspace = true + +[build-dependencies] +anyhow.workspace = true +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true +serde_json.workspace = true +heck.workspace = true +serde.workspace = true +valence_build_utils.workspace = true diff --git a/crates/valence_entity/README.md b/crates/valence_entity/README.md new file mode 100644 index 0000000..18a76d1 --- /dev/null +++ b/crates/valence_entity/README.md @@ -0,0 +1,5 @@ +# valence_entity + +Components and systems concerning Minecraft entities. This includes "zombie", "chicken", "player", etc. + +Bundles of components are used to spawn entities. Each entity type gets its own module here. diff --git a/crates/valence/build/entity.rs b/crates/valence_entity/build.rs similarity index 81% rename from crates/valence/build/entity.rs rename to crates/valence_entity/build.rs index 06eb974..1c21e85 100644 --- a/crates/valence/build/entity.rs +++ b/crates/valence_entity/build.rs @@ -5,8 +5,7 @@ use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; - -use crate::ident; +use valence_build_utils::{ident, rerun_if_changed, write_generated_file}; #[derive(Deserialize, Clone, Debug)] struct Entity { @@ -129,29 +128,29 @@ impl Value { Value::Long(_) => quote!(i64), Value::Float(_) => quote!(f32), Value::String(_) => quote!(String), - Value::TextComponent(_) => quote!(crate::protocol::text::Text), - Value::OptionalTextComponent(_) => quote!(Option), - Value::ItemStack(_) => quote!(crate::protocol::item::ItemStack), + Value::TextComponent(_) => quote!(valence_core::text::Text), + Value::OptionalTextComponent(_) => quote!(Option), + Value::ItemStack(_) => quote!(valence_core::item::ItemStack), Value::Boolean(_) => quote!(bool), - Value::Rotation { .. } => quote!(crate::entity::EulerAngle), - Value::BlockPos(_) => quote!(crate::protocol::block_pos::BlockPos), - Value::OptionalBlockPos(_) => quote!(Option), - Value::Facing(_) => quote!(crate::protocol::types::Direction), + Value::Rotation { .. } => quote!(crate::EulerAngle), + Value::BlockPos(_) => quote!(valence_core::block_pos::BlockPos), + Value::OptionalBlockPos(_) => quote!(Option), + Value::Facing(_) => quote!(valence_core::direction::Direction), Value::OptionalUuid(_) => quote!(Option<::uuid::Uuid>), - Value::BlockState(_) => quote!(crate::protocol::block::BlockState), - Value::OptionalBlockState(_) => quote!(crate::protocol::block::BlockState), - Value::NbtCompound(_) => quote!(crate::nbt::Compound), - Value::Particle(_) => quote!(crate::protocol::packet::s2c::play::particle::Particle), - Value::VillagerData { .. } => quote!(crate::entity::VillagerData), + Value::BlockState(_) => quote!(valence_block::BlockState), + Value::OptionalBlockState(_) => quote!(valence_block::BlockState), + Value::NbtCompound(_) => quote!(valence_nbt::Compound), + Value::Particle(_) => quote!(valence_core::packet::s2c::play::particle::Particle), + Value::VillagerData { .. } => quote!(crate::VillagerData), Value::OptionalInt(_) => quote!(Option), - Value::EntityPose(_) => quote!(crate::entity::Pose), - Value::CatVariant(_) => quote!(crate::entity::CatKind), - Value::FrogVariant(_) => quote!(crate::entity::FrogKind), + Value::EntityPose(_) => quote!(crate::Pose), + Value::CatVariant(_) => quote!(crate::CatKind), + Value::FrogVariant(_) => quote!(crate::FrogKind), Value::OptionalGlobalPos(_) => quote!(()), // TODO - Value::PaintingVariant(_) => quote!(crate::entity::PaintingKind), - Value::SnifferState(_) => quote!(crate::entity::SnifferState), - Value::Vector3f { .. } => quote!(::glam::f32::Vec3), - Value::Quaternionf { .. } => quote!(::glam::f32::Quat), + Value::PaintingVariant(_) => quote!(crate::PaintingKind), + Value::SnifferState(_) => quote!(crate::SnifferState), + Value::Vector3f { .. } => quote!(glam::f32::Vec3), + Value::Quaternionf { .. } => quote!(glam::f32::Quat), } } @@ -164,7 +163,7 @@ impl Value { Value::String(s) => quote!(#s.to_owned()), Value::TextComponent(txt) => { assert!(txt.is_empty()); - quote!(crate::protocol::text::Text::default()) + quote!(valence_core::text::Text::default()) } Value::OptionalTextComponent(t) => { assert!(t.is_none()); @@ -172,18 +171,18 @@ impl Value { } Value::ItemStack(stack) => { assert_eq!(stack, "1 air"); - quote!(crate::protocol::item::ItemStack::default()) + quote!(valence_core::item::ItemStack::default()) } Value::Boolean(b) => quote!(#b), Value::Rotation { pitch, yaw, roll } => quote! { - crate::entity::EulerAngle { + crate::EulerAngle { pitch: #pitch, yaw: #yaw, roll: #roll, } }, Value::BlockPos(BlockPos { x, y, z }) => { - quote!(crate::protocol::block_pos::BlockPos { x: #x, y: #y, z: #z }) + quote!(valence_core::block_pos::BlockPos { x: #x, y: #y, z: #z }) } Value::OptionalBlockPos(pos) => { assert!(pos.is_none()); @@ -191,26 +190,26 @@ impl Value { } Value::Facing(f) => { let variant = ident(f.to_pascal_case()); - quote!(crate::protocol::types::Direction::#variant) + quote!(valence_core::direction::Direction::#variant) } Value::OptionalUuid(uuid) => { assert!(uuid.is_none()); quote!(None) } Value::BlockState(_) => { - quote!(crate::protocol::block::BlockState::default()) + quote!(valence_block::BlockState::default()) } Value::OptionalBlockState(bs) => { assert!(bs.is_none()); - quote!(crate::protocol::block::BlockState::default()) + quote!(valence_block::BlockState::default()) } Value::NbtCompound(s) => { assert_eq!(s, "{}"); - quote!(crate::nbt::Compound::default()) + quote!(valence_nbt::Compound::default()) } Value::Particle(p) => { let variant = ident(p.to_pascal_case()); - quote!(crate::protocol::packet::s2c::play::particle::Particle::#variant) + quote!(valence_core::packet::s2c::play::particle::Particle::#variant) } Value::VillagerData { typ, @@ -220,9 +219,9 @@ impl Value { let typ = ident(typ.to_pascal_case()); let profession = ident(profession.to_pascal_case()); quote! { - crate::entity::VillagerData { - kind: crate::entity::VillagerKind::#typ, - profession: crate::entity::VillagerProfession::#profession, + crate::VillagerData { + kind: crate::VillagerKind::#typ, + profession: crate::VillagerProfession::#profession, level: #level, } } @@ -233,28 +232,28 @@ impl Value { } Value::EntityPose(p) => { let variant = ident(p.to_pascal_case()); - quote!(crate::entity::Pose::#variant) + quote!(crate::Pose::#variant) } Value::CatVariant(c) => { let variant = ident(c.to_pascal_case()); - quote!(crate::entity::CatKind::#variant) + quote!(crate::CatKind::#variant) } Value::FrogVariant(f) => { let variant = ident(f.to_pascal_case()); - quote!(crate::entity::FrogKind::#variant) + quote!(crate::FrogKind::#variant) } Value::OptionalGlobalPos(_) => quote!(()), Value::PaintingVariant(p) => { let variant = ident(p.to_pascal_case()); - quote!(crate::entity::PaintingKind::#variant) + quote!(crate::PaintingKind::#variant) } Value::SnifferState(s) => { let state = ident(s.to_pascal_case()); - quote!(crate::entity::SnifferState::#state) + quote!(crate::SnifferState::#state) } - Value::Vector3f { x, y, z } => quote!(::glam::f32::Vec3::new(#x, #y, #z)), + Value::Vector3f { x, y, z } => quote!(glam::f32::Vec3::new(#x, #y, #z)), Value::Quaternionf { x, y, z, w } => quote! { - ::glam::f32::Quat::from_xyzw(#x, #y, #z, #w) + glam::f32::Quat::from_xyzw(#x, #y, #z, #w) }, } } @@ -271,14 +270,20 @@ impl Value { type Entities = BTreeMap; -pub fn build() -> anyhow::Result { +pub fn main() -> anyhow::Result<()> { + rerun_if_changed(["../../extracted/misc.json", "../../extracted/entities.json"]); + + write_generated_file(build()?, "entity.rs") +} + +fn build() -> anyhow::Result { let entity_types = - serde_json::from_str::(include_str!("../../../extracted/misc.json")) + serde_json::from_str::(include_str!("../../extracted/misc.json")) .context("failed to deserialize misc.json")? .entity_type; let entities: Entities = - serde_json::from_str::(include_str!("../../../extracted/entities.json")) + serde_json::from_str::(include_str!("../../extracted/entities.json")) .context("failed to deserialize entities.json")? .into_iter() .collect(); @@ -504,6 +509,40 @@ pub fn build() -> anyhow::Result { }]); } + #[derive(Deserialize, Debug)] + struct MiscEntityData { + entity_status: BTreeMap, + entity_animation: BTreeMap, + } + + let misc_entity_data: MiscEntityData = + serde_json::from_str(include_str!("../../extracted/misc.json"))?; + + let entity_status_variants = misc_entity_data + .entity_status + .into_iter() + .map(|(name, code)| { + let name = ident(name.to_pascal_case()); + let code = code as isize; + + quote! { + #name = #code, + } + }); + + let entity_animation_variants = + misc_entity_data + .entity_animation + .into_iter() + .map(|(name, code)| { + let name = ident(name.to_pascal_case()); + let code = code as isize; + + quote! { + #name = #code, + } + }); + Ok(quote! { #modules @@ -541,12 +580,24 @@ pub fn build() -> anyhow::Result { } } + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + pub enum EntityStatus { + #(#entity_status_variants)* + } + + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + pub enum EntityAnimation { + #(#entity_animation_variants)* + } + fn add_tracked_data_systems(app: &mut App) { #systems #( app.add_system( - #system_names.before(WriteUpdatePacketsToInstancesSet).in_base_set(CoreSet::PostUpdate) + #system_names + .in_set(UpdateTrackedDataSet) + .ambiguous_with(UpdateTrackedDataSet) ); )* } diff --git a/crates/valence/src/entity/hitbox.rs b/crates/valence_entity/src/hitbox.rs similarity index 100% rename from crates/valence/src/entity/hitbox.rs rename to crates/valence_entity/src/hitbox.rs diff --git a/crates/valence/src/entity.rs b/crates/valence_entity/src/lib.rs similarity index 71% rename from crates/valence/src/entity.rs rename to crates/valence_entity/src/lib.rs index 2078326..0d35823 100644 --- a/crates/valence/src/entity.rs +++ b/crates/valence_entity/src/lib.rs @@ -1,26 +1,312 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use std::num::Wrapping; use std::ops::Range; use bevy_app::{App, CoreSet, Plugin}; use bevy_ecs::prelude::*; -use glam::Vec3; +use glam::{DVec3, Vec3}; use paste::paste; use rustc_hash::FxHashMap; use tracing::warn; use uuid::Uuid; -pub use valence_protocol::types::Direction; -use valence_protocol::var_int::VarInt; -use valence_protocol::{Decode, Encode}; +use valence_core::chunk_pos::ChunkPos; +use valence_core::despawn::Despawned; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::{Decode, Encode}; +use valence_core::uuid::UniqueId; +use valence_core::DEFAULT_TPS; -use crate::client::FlushPacketsSet; -use crate::component::{ - Despawned, Location, Look, OldLocation, OldPosition, OnGround, Position, UniqueId, -}; -use crate::instance::WriteUpdatePacketsToInstancesSet; - -include!(concat!(env!("OUT_DIR"), "/entity_event.rs")); include!(concat!(env!("OUT_DIR"), "/entity.rs")); +pub struct EntityPlugin; + +/// When new Minecraft entities are initialized and added to +/// [`EntityManager`]. +/// +/// Systems that need Minecraft entities to be in a valid state should run +/// _after_ this set. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct InitEntitiesSet; + +/// When tracked data is written to the entity's [`TrackedData`] component. +/// Systems that modify tracked data should run _before_ this. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UpdateTrackedDataSet; + +/// When entities are updated and changes from the current tick are cleared. +/// Systems that need to observe changes to entities (Such as the difference +/// between [`Position`] and [`OldPosition`]) should run _before_ this set (and +/// probably after [`InitEntitiesSet`]). +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ClearEntityChangesSet; + +impl Plugin for EntityPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(EntityManager::new()) + .configure_sets(( + InitEntitiesSet.in_base_set(CoreSet::PostUpdate), + UpdateTrackedDataSet.in_base_set(CoreSet::PostUpdate), + ClearEntityChangesSet + .after(InitEntitiesSet) + .after(UpdateTrackedDataSet) + .in_base_set(CoreSet::PostUpdate), + )) + .add_systems( + (init_entities, remove_despawned_from_manager) + .chain() + .in_set(InitEntitiesSet), + ) + .add_systems( + ( + clear_status_changes, + clear_animation_changes, + clear_tracked_data_changes, + update_old_position, + update_old_location, + ) + .in_set(ClearEntityChangesSet), + ); + + add_tracked_data_systems(app); + } +} + +fn update_old_position(mut query: Query<(&Position, &mut OldPosition)>) { + for (pos, mut old_pos) in &mut query { + old_pos.0 = pos.0; + } +} + +fn update_old_location(mut query: Query<(&Location, &mut OldLocation)>) { + for (loc, mut old_loc) in &mut query { + old_loc.0 = loc.0; + } +} + +fn init_entities( + mut entities: Query< + ( + Entity, + &mut EntityId, + &mut UniqueId, + &Position, + &mut OldPosition, + ), + Added, + >, + mut manager: ResMut, +) { + for (entity, mut id, uuid, pos, mut old_pos) in &mut entities { + *old_pos = OldPosition::new(pos.0); + + if *id == EntityId::default() { + *id = manager.next_id(); + } + + if let Some(conflict) = manager.id_to_entity.insert(id.0, entity) { + warn!( + "entity {entity:?} has conflicting entity ID of {} with entity {conflict:?}", + id.0 + ); + } + + if let Some(conflict) = manager.uuid_to_entity.insert(uuid.0, entity) { + warn!( + "entity {entity:?} has conflicting UUID of {} with entity {conflict:?}", + uuid.0 + ); + } + } +} + +#[allow(clippy::type_complexity)] +fn remove_despawned_from_manager( + entities: Query<(&EntityId, &UniqueId), (With, With)>, + mut manager: ResMut, +) { + for (id, uuid) in &entities { + manager.id_to_entity.remove(&id.0); + manager.uuid_to_entity.remove(&uuid.0); + } +} + +fn clear_status_changes(mut statuses: Query<&mut EntityStatuses, Changed>) { + for mut statuses in &mut statuses { + statuses.0 = 0; + } +} + +fn clear_animation_changes( + mut animations: Query<&mut EntityAnimations, Changed>, +) { + for mut animations in &mut animations { + animations.0 = 0; + } +} + +fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed>) { + for mut tracked_data in &mut tracked_data { + tracked_data.clear_update_values(); + } +} + +/// Contains the `Instance` an entity is located in. For the coordinates +/// within the instance, see [`Position`]. +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] +pub struct Location(pub Entity); + +impl Default for Location { + fn default() -> Self { + Self(Entity::PLACEHOLDER) + } +} + +impl PartialEq for Location { + fn eq(&self, other: &OldLocation) -> bool { + self.0 == other.0 + } +} + +/// The value of [`Location`] from the end of the previous tick. +/// +/// **NOTE**: You should not modify this component after the entity is spawned. +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] +pub struct OldLocation(Entity); + +impl OldLocation { + pub fn new(instance: Entity) -> Self { + Self(instance) + } + + pub fn get(&self) -> Entity { + self.0 + } +} + +impl Default for OldLocation { + fn default() -> Self { + Self(Entity::PLACEHOLDER) + } +} + +impl PartialEq for OldLocation { + fn eq(&self, other: &Location) -> bool { + self.0 == other.0 + } +} + +#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] +pub struct Position(pub DVec3); + +impl Position { + pub fn new(pos: impl Into) -> Self { + Self(pos.into()) + } + + pub fn chunk_pos(&self) -> ChunkPos { + ChunkPos::from_dvec3(self.0) + } + + pub fn get(self) -> DVec3 { + self.0 + } + + pub fn set(&mut self, pos: impl Into) { + self.0 = pos.into(); + } +} + +impl PartialEq for Position { + fn eq(&self, other: &OldPosition) -> bool { + self.0 == other.0 + } +} + +/// The value of [`Position`] from the end of the previous tick. +/// +/// **NOTE**: You should not modify this component after the entity is spawned. +#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] +pub struct OldPosition(DVec3); + +impl OldPosition { + pub fn new(pos: impl Into) -> Self { + Self(pos.into()) + } + + pub fn get(self) -> DVec3 { + self.0 + } + + pub fn chunk_pos(self) -> ChunkPos { + ChunkPos::from_dvec3(self.0) + } +} + +impl PartialEq for OldPosition { + fn eq(&self, other: &Position) -> bool { + self.0 == other.0 + } +} + +/// Describes the direction an entity is looking using pitch and yaw angles. +#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] +pub struct Look { + /// The yaw angle in degrees. + pub yaw: f32, + /// The pitch angle in degrees. + pub pitch: f32, +} + +impl Look { + pub const fn new(yaw: f32, pitch: f32) -> Self { + Self { yaw, pitch } + } + + /// Gets a normalized direction vector from the yaw and pitch. + pub fn vec(self) -> Vec3 { + let (yaw_sin, yaw_cos) = (self.yaw + 90.0).to_radians().sin_cos(); + let (pitch_sin, pitch_cos) = (-self.pitch).to_radians().sin_cos(); + + Vec3::new(yaw_cos * pitch_cos, pitch_sin, yaw_sin * pitch_cos) + } + + /// Sets the yaw and pitch using a normalized direction vector. + pub fn set_vec(&mut self, dir: Vec3) { + debug_assert!( + dir.is_normalized(), + "the direction vector should be normalized" + ); + + // Preserve the current yaw if we're looking straight up or down. + if dir.x != 0.0 || dir.z != 0.0 { + self.yaw = f32::atan2(dir.z, dir.x).to_degrees() - 90.0; + } + + self.pitch = -(dir.y).asin().to_degrees(); + } +} + +#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] +pub struct OnGround(pub bool); + /// A Minecraft entity's ID according to the protocol. /// /// IDs should be _unique_ for the duration of the server and _constant_ for @@ -52,6 +338,15 @@ pub struct HeadYaw(pub f32); #[derive(Component, Copy, Clone, Default, Debug)] pub struct Velocity(pub Vec3); +impl Velocity { + pub fn to_packet_units(self) -> [i16; 3] { + // The saturating casts to i16 are desirable. + (8000.0 / DEFAULT_TPS.get() as f32 * self.0) + .to_array() + .map(|v| v as i16) + } +} + #[derive(Component, Copy, Clone, Default, Debug)] pub struct EntityStatuses(pub u64); @@ -102,12 +397,12 @@ pub struct ObjectData(pub i32); /// The range of packet bytes for this entity within the cell the entity is /// located in. For internal use only. #[derive(Component, Default, Debug)] -pub struct PacketByteRange(pub(crate) Range); +pub struct PacketByteRange(pub Range); /// Cache for all the tracked data of an entity. Used for the /// [`EntityTrackerUpdateS2c`][packet] packet. /// -/// [packet]: valence_protocol::packet::s2c::play::EntityTrackerUpdateS2c +/// [packet]: valence_core::packet::s2c::play::EntityTrackerUpdateS2c #[derive(Component, Default, Debug)] pub struct TrackedData { init_data: Vec, @@ -122,7 +417,7 @@ impl TrackedData { /// [`EntityTrackerUpdateS2c`][packet] packet. This is used when the entity /// enters the view of a client. /// - /// [packet]: valence_protocol::packet::s2c::play::EntityTrackerUpdateS2c + /// [packet]: valence_core::packet::s2c::play::EntityTrackerUpdateS2c pub fn init_data(&self) -> Option<&[u8]> { if self.init_data.len() > 1 { Some(&self.init_data) @@ -135,7 +430,7 @@ impl TrackedData { /// [`EntityTrackerUpdateS2c`][packet] packet. This is used when tracked /// data is changed and the client is already in view of the entity. /// - /// [packet]: valence_protocol::packet::s2c::play::EntityTrackerUpdateS2c + /// [packet]: valence_core::packet::s2c::play::EntityTrackerUpdateS2c pub fn update_data(&self) -> Option<&[u8]> { if self.update_data.len() > 1 { Some(&self.update_data) @@ -572,105 +867,6 @@ flags! { } } -pub(crate) struct EntityPlugin; - -/// When new Minecraft entities are initialized and added to -/// [`EntityManager`]. Systems that need all Minecraft entities to be in a -/// valid state should run after this. -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub(crate) struct InitEntitiesSet; - -impl Plugin for EntityPlugin { - fn build(&self, app: &mut App) { - app.insert_resource(EntityManager::new()) - .configure_set(InitEntitiesSet.in_base_set(CoreSet::PostUpdate)) - .add_system(init_entities.in_set(InitEntitiesSet)) - .add_system( - remove_despawned_from_manager - .in_base_set(CoreSet::PostUpdate) - .after(init_entities), - ) - .add_systems( - (clear_status_changes, clear_animation_changes) - .after(WriteUpdatePacketsToInstancesSet) - .in_base_set(CoreSet::PostUpdate), - ) - .add_system( - clear_tracked_data_changes - .after(FlushPacketsSet) - .in_base_set(CoreSet::PostUpdate), - ); - - add_tracked_data_systems(app); - } -} - -fn init_entities( - mut entities: Query< - ( - Entity, - &mut EntityId, - &mut UniqueId, - &Position, - &mut OldPosition, - ), - Added, - >, - mut manager: ResMut, -) { - for (entity, mut id, uuid, pos, mut old_pos) in &mut entities { - *old_pos = OldPosition::new(pos.0); - - if *id == EntityId::default() { - *id = manager.next_id(); - } - - if let Some(conflict) = manager.id_to_entity.insert(id.0, entity) { - warn!( - "entity {entity:?} has conflicting entity ID of {} with entity {conflict:?}", - id.0 - ); - } - - if let Some(conflict) = manager.uuid_to_entity.insert(uuid.0, entity) { - warn!( - "entity {entity:?} has conflicting UUID of {} with entity {conflict:?}", - uuid.0 - ); - } - } -} - -fn remove_despawned_from_manager( - entities: Query<(&EntityId, &UniqueId), (With, With)>, - mut manager: ResMut, -) { - for (id, uuid) in &entities { - manager.id_to_entity.remove(&id.0); - manager.uuid_to_entity.remove(&uuid.0); - } -} - -fn clear_status_changes(mut statuses: Query<&mut EntityStatuses, Changed>) { - for mut statuses in &mut statuses { - statuses.0 = 0; - } -} - -fn clear_animation_changes( - mut animations: Query<&mut EntityAnimations, Changed>, -) { - for mut animations in &mut animations { - animations.0 = 0; - } -} - -fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed>) { - for mut tracked_data in &mut tracked_data { - tracked_data.clear_update_values(); - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/valence_instance/Cargo.toml b/crates/valence_instance/Cargo.toml new file mode 100644 index 0000000..5e277fa --- /dev/null +++ b/crates/valence_instance/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "valence_instance" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +arrayvec.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +glam.workspace = true +num-integer.workspace = true +parking_lot.workspace = true +rand.workspace = true +rustc-hash.workspace = true +valence_biome.workspace = true +valence_block.workspace = true +valence_core.workspace = true +valence_dimension.workspace = true +valence_entity.workspace = true +valence_nbt.workspace = true diff --git a/crates/valence_instance/README.md b/crates/valence_instance/README.md new file mode 100644 index 0000000..8affa99 --- /dev/null +++ b/crates/valence_instance/README.md @@ -0,0 +1,3 @@ +# valence_instance + +Containers for chunks and entities. Instances are analogous to "levels" or "worlds" in Minecraft's parlance. diff --git a/crates/valence/src/instance/chunk.rs b/crates/valence_instance/src/chunk.rs similarity index 97% rename from crates/valence/src/instance/chunk.rs rename to crates/valence_instance/src/chunk.rs index 8cee7d4..9828de3 100644 --- a/crates/valence/src/instance/chunk.rs +++ b/crates/valence_instance/src/chunk.rs @@ -3,25 +3,23 @@ use std::collections::btree_map::Entry; use std::collections::{BTreeMap, BTreeSet}; use std::sync::atomic::{AtomicBool, Ordering}; -// Using nonstandard mutex to avoid poisoning API. -use parking_lot::Mutex; -use valence_nbt::{compound, Compound}; -use valence_protocol::block::{BlockEntityKind, BlockState}; -use valence_protocol::block_pos::BlockPos; -use valence_protocol::packet::s2c::play::chunk_data::ChunkDataBlockEntity; -use valence_protocol::packet::s2c::play::{ +use parking_lot::Mutex; // Using nonstandard mutex to avoid poisoning API. +use valence_biome::BiomeId; +use valence_block::{BlockEntityKind, BlockState}; +use valence_core::block_pos::BlockPos; +use valence_core::chunk_pos::ChunkPos; +use valence_core::packet::encode::{PacketWriter, WritePacket}; +use valence_core::packet::s2c::play::chunk_data::ChunkDataBlockEntity; +use valence_core::packet::s2c::play::{ BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c, }; -use valence_protocol::var_int::VarInt; -use valence_protocol::var_long::VarLong; -use valence_protocol::Encode; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::var_long::VarLong; +use valence_core::packet::Encode; +use valence_nbt::{compound, Compound}; -use crate::biome::BiomeId; -use crate::instance::paletted_container::PalettedContainer; -use crate::instance::InstanceInfo; -use crate::packet::{PacketWriter, WritePacket}; -use crate::util::bit_width; -use crate::view::ChunkPos; +use crate::paletted_container::PalettedContainer; +use crate::{bit_width, InstanceInfo}; /// A chunk is a 16x16-meter segment of a world with a variable height. Chunks /// primarily contain blocks, biomes, and block entities. @@ -300,7 +298,8 @@ impl Chunk { } /// Marks this chunk as being seen by a client. - pub(crate) fn mark_viewed(&self) { + #[doc(hidden)] + pub fn mark_viewed(&self) { self.viewed.store(true, Ordering::Relaxed); } @@ -378,7 +377,7 @@ impl Chunk { writer.write_packet(&BlockEntityUpdateS2c { position: BlockPos::new(global_x, global_y, global_z), - kind: block_entity.kind, + kind: VarInt(block_entity.kind as i32), data: Cow::Borrowed(&block_entity.nbt), }) } @@ -387,7 +386,8 @@ impl Chunk { /// Writes the chunk data packet for this chunk with the given position. /// This will initialize the chunk for the client. - pub(crate) fn write_init_packets( + #[doc(hidden)] + pub fn write_init_packets( &self, info: &InstanceInfo, pos: ChunkPos, @@ -442,7 +442,7 @@ impl Chunk { ChunkDataBlockEntity { packed_xz: ((x << 4) | z) as i8, y, - kind: block_entity.kind, + kind: VarInt(block_entity.kind as i32), data: Cow::Borrowed(&block_entity.nbt), } }) @@ -453,8 +453,7 @@ impl Chunk { }; writer.write_packet(&ChunkDataS2c { - chunk_x: pos.x, - chunk_z: pos.z, + pos, heightmaps: Cow::Owned(heightmaps), blocks_and_biomes: scratch, block_entities: Cow::Borrowed(&block_entities), @@ -919,10 +918,9 @@ impl Chunk { #[cfg(test)] mod tests { - use valence_protocol::block::BlockEntityKind; + use valence_block::{BlockEntityKind, BlockState}; use super::*; - use crate::protocol::block::BlockState; fn check(chunk: &Chunk, total_expected_change_count: usize) { assert!(!chunk.refresh, "chunk should not be refreshed for the test"); diff --git a/crates/valence/src/instance/chunk_entry.rs b/crates/valence_instance/src/chunk_entry.rs similarity index 100% rename from crates/valence/src/instance/chunk_entry.rs rename to crates/valence_instance/src/chunk_entry.rs diff --git a/crates/valence/src/instance.rs b/crates/valence_instance/src/lib.rs similarity index 86% rename from crates/valence/src/instance.rs rename to crates/valence_instance/src/lib.rs index e89e7f6..670b83f 100644 --- a/crates/valence/src/instance.rs +++ b/crates/valence_instance/src/lib.rs @@ -1,65 +1,422 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use std::borrow::Cow; use std::collections::hash_map::Entry; use std::collections::BTreeSet; use std::iter::FusedIterator; use std::mem; -use bevy_app::{CoreSet, Plugin}; +use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::query::WorldQuery; pub use chunk::{Block, BlockEntity, BlockMut, BlockRef, Chunk}; pub use chunk_entry::*; use glam::{DVec3, Vec3}; -use num::integer::div_ceil; +use num_integer::div_ceil; use rustc_hash::FxHashMap; -use valence_protocol::array::LengthPrefixedArray; -use valence_protocol::block_pos::BlockPos; -use valence_protocol::byte_angle::ByteAngle; -use valence_protocol::ident::Ident; -use valence_protocol::packet::s2c::play::particle::Particle; -use valence_protocol::packet::s2c::play::{ +use valence_biome::Biome; +use valence_core::block_pos::BlockPos; +use valence_core::chunk_pos::ChunkPos; +use valence_core::despawn::Despawned; +use valence_core::ident::Ident; +use valence_core::packet::array::LengthPrefixedArray; +use valence_core::packet::byte_angle::ByteAngle; +use valence_core::packet::encode::{PacketWriter, WritePacket}; +use valence_core::packet::s2c::play::particle::Particle; +use valence_core::packet::s2c::play::{ EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelative, OverlayMessageS2c, ParticleS2c, PlaySoundS2c, Rotate, RotateAndMoveRelative, }; -use valence_protocol::sound::Sound; -use valence_protocol::text::Text; -use valence_protocol::types::SoundCategory; -use valence_protocol::var_int::VarInt; -use valence_protocol::Packet; - -use crate::biome::Biome; -use crate::client::FlushPacketsSet; -use crate::component::{Despawned, Location, Look, OldLocation, OldPosition, OnGround, Position}; -use crate::dimension::DimensionType; -use crate::entity::{ - EntityAnimations, EntityId, EntityKind, EntityStatuses, HeadYaw, InitEntitiesSet, - PacketByteRange, TrackedData, Velocity, +use valence_core::packet::var_int::VarInt; +use valence_core::packet::Packet; +use valence_core::sound::{Sound, SoundCategory}; +use valence_core::text::Text; +use valence_core::Server; +use valence_dimension::DimensionType; +use valence_entity::{ + EntityAnimations, EntityId, EntityKind, EntityStatuses, HeadYaw, InitEntitiesSet, Location, + Look, OldLocation, OldPosition, OnGround, PacketByteRange, Position, TrackedData, + UpdateTrackedDataSet, Velocity, }; -use crate::packet::{PacketWriter, WritePacket}; -use crate::server::{Server, SharedServer}; -use crate::util::velocity_to_packet_units; -use crate::view::ChunkPos; mod chunk; mod chunk_entry; mod paletted_container; +pub struct InstancePlugin; + +/// When Minecraft entity changes are written to the packet buffers of chunks. +/// Systems that read from the packet buffer of chunks should run _after_ this. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct WriteUpdatePacketsToInstancesSet; + +/// When instances are updated and changes from the current tick are cleared. +/// Systems that read changes from instances should run _before_ this. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ClearInstanceChangesSet; + +impl Plugin for InstancePlugin { + fn build(&self, app: &mut App) { + app.configure_sets(( + WriteUpdatePacketsToInstancesSet + .in_base_set(CoreSet::PostUpdate) + .after(InitEntitiesSet) + .after(UpdateTrackedDataSet), + ClearInstanceChangesSet + .after(WriteUpdatePacketsToInstancesSet) + .in_base_set(CoreSet::PostUpdate), + )) + .add_system( + // This can run at the same time as entity init because we're only looking at position + // + location. + update_entity_cell_positions + .in_base_set(CoreSet::PostUpdate) + .before(WriteUpdatePacketsToInstancesSet), + ) + .add_system( + write_update_packets_to_instances + .after(update_entity_cell_positions) + .in_set(WriteUpdatePacketsToInstancesSet), + ) + .add_system(clear_instance_changes.in_set(ClearInstanceChangesSet)); + + #[cfg(debug_assertions)] + app.add_system(check_instance_invariants.in_base_set(CoreSet::PostUpdate)); + } +} + +/// Handles entities moving from one chunk to another. +#[allow(clippy::type_complexity)] +fn update_entity_cell_positions( + entities: Query< + ( + Entity, + &Position, + &OldPosition, + &Location, + &OldLocation, + Option<&Despawned>, + ), + (With, Or<(Changed, With)>), + >, + mut instances: Query<&mut Instance>, +) { + for (entity, pos, old_pos, loc, old_loc, despawned) in &entities { + let pos = ChunkPos::at(pos.0.x, pos.0.z); + let old_pos = ChunkPos::at(old_pos.get().x, old_pos.get().z); + + if despawned.is_some() { + // Entity was deleted. Remove it from the chunk it was in, if it was in a chunk + // at all. + if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) { + if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) { + if old_cell.entities.remove(&entity) { + old_cell.outgoing.push((entity, None)); + } + } + } + } else if old_loc.get() != loc.0 { + // Entity changed the instance it is in. Remove it from old cell and + // insert it in the new cell. + + // TODO: skip marker entity? + + if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) { + if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) { + if old_cell.entities.remove(&entity) { + old_cell.outgoing.push((entity, None)); + } + } + } + + if let Ok(mut instance) = instances.get_mut(loc.0) { + match instance.partition.entry(pos) { + Entry::Occupied(oe) => { + let cell = oe.into_mut(); + if cell.entities.insert(entity) { + cell.incoming.push((entity, None)); + } + } + Entry::Vacant(ve) => { + ve.insert(PartitionCell { + chunk: None, + chunk_removed: false, + entities: BTreeSet::from([entity]), + incoming: vec![(entity, None)], + outgoing: vec![], + packet_buf: vec![], + }); + } + } + } + } else if pos != old_pos { + // Entity changed its chunk position without changing instances. Remove + // it from old cell and insert it in new cell. + + // TODO: skip marker entity? + + if let Ok(mut instance) = instances.get_mut(loc.0) { + if let Some(old_cell) = instance.partition.get_mut(&old_pos) { + if old_cell.entities.remove(&entity) { + old_cell.outgoing.push((entity, Some(pos))); + } + } + + match instance.partition.entry(pos) { + Entry::Occupied(oe) => { + let cell = oe.into_mut(); + if cell.entities.insert(entity) { + cell.incoming.push((entity, Some(old_pos))); + } + } + Entry::Vacant(ve) => { + ve.insert(PartitionCell { + chunk: None, + chunk_removed: false, + entities: BTreeSet::from([entity]), + incoming: vec![(entity, Some(old_pos))], + outgoing: vec![], + packet_buf: vec![], + }); + } + } + } + } else { + // The entity didn't change its chunk position so there is nothing + // we need to do. + } + } +} + +/// Writes update packets from entities and chunks into each cell's packet +/// buffer. +fn write_update_packets_to_instances( + mut instances: Query<&mut Instance>, + mut entities: Query, Without)>, + server: Res, +) { + let mut scratch_1 = vec![]; + let mut scratch_2 = vec![]; + + for instance in &mut instances { + let instance = instance.into_inner(); + + for (&pos, cell) in &mut instance.partition { + // Cache chunk update packets into the packet buffer of this cell. + if let Some(chunk) = &mut cell.chunk { + let writer = PacketWriter::new( + &mut cell.packet_buf, + server.compression_threshold(), + &mut scratch_2, + ); + + chunk.write_update_packets(writer, &mut scratch_1, pos, &instance.info); + + chunk.clear_viewed(); + } + + // Cache entity update packets into the packet buffer of this cell. + for &entity in &cell.entities { + let mut entity = entities + .get_mut(entity) + .expect("missing entity in partition cell"); + + let start = cell.packet_buf.len(); + + let writer = PacketWriter::new( + &mut cell.packet_buf, + server.compression_threshold(), + &mut scratch_2, + ); + + entity.write_update_packets(writer); + + let end = cell.packet_buf.len(); + + entity.packet_byte_range.0 = start..end; + } + } + } +} + +#[derive(WorldQuery)] +#[world_query(mutable)] +struct UpdateEntityQuery { + id: &'static EntityId, + pos: &'static Position, + old_pos: &'static OldPosition, + loc: &'static Location, + old_loc: &'static OldLocation, + look: Ref<'static, Look>, + head_yaw: Ref<'static, HeadYaw>, + on_ground: &'static OnGround, + velocity: Ref<'static, Velocity>, + tracked_data: &'static TrackedData, + statuses: &'static EntityStatuses, + animations: &'static EntityAnimations, + packet_byte_range: &'static mut PacketByteRange, +} + +impl UpdateEntityQueryItem<'_> { + fn write_update_packets(&self, mut writer: impl WritePacket) { + // TODO: @RJ I saw you're using UpdateEntityPosition and UpdateEntityRotation sometimes. These two packets are actually broken on the client and will erase previous position/rotation https://bugs.mojang.com/browse/MC-255263 -Moulberry + + let entity_id = VarInt(self.id.get()); + + let position_delta = self.pos.0 - self.old_pos.get(); + let needs_teleport = position_delta.abs().max_element() >= 8.0; + let changed_position = self.pos.0 != self.old_pos.get(); + + if changed_position && !needs_teleport && self.look.is_changed() { + writer.write_packet(&RotateAndMoveRelative { + entity_id, + delta: (position_delta * 4096.0).to_array().map(|v| v as i16), + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + on_ground: self.on_ground.0, + }); + } else { + if changed_position && !needs_teleport { + writer.write_packet(&MoveRelative { + entity_id, + delta: (position_delta * 4096.0).to_array().map(|v| v as i16), + on_ground: self.on_ground.0, + }); + } + + if self.look.is_changed() { + writer.write_packet(&Rotate { + entity_id, + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + on_ground: self.on_ground.0, + }); + } + } + + if needs_teleport { + writer.write_packet(&EntityPositionS2c { + entity_id, + position: self.pos.0, + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + on_ground: self.on_ground.0, + }); + } + + if self.velocity.is_changed() { + writer.write_packet(&EntityVelocityUpdateS2c { + entity_id, + velocity: self.velocity.to_packet_units(), + }); + } + + if self.head_yaw.is_changed() { + writer.write_packet(&EntitySetHeadYawS2c { + entity_id, + head_yaw: ByteAngle::from_degrees(self.head_yaw.0), + }); + } + + if let Some(update_data) = self.tracked_data.update_data() { + writer.write_packet(&EntityTrackerUpdateS2c { + entity_id, + metadata: update_data.into(), + }); + } + + if self.statuses.0 != 0 { + for i in 0..mem::size_of_val(self.statuses) { + if (self.statuses.0 >> i) & 1 == 1 { + writer.write_packet(&EntityStatusS2c { + entity_id: entity_id.0, + entity_status: i as u8, + }); + } + } + } + + if self.animations.0 != 0 { + for i in 0..mem::size_of_val(self.animations) { + if (self.animations.0 >> i) & 1 == 1 { + writer.write_packet(&EntityAnimationS2c { + entity_id, + animation: i as u8, + }); + } + } + } + } +} + +fn clear_instance_changes(mut instances: Query<&mut Instance>) { + for mut instance in &mut instances { + instance.partition.retain(|_, cell| { + cell.packet_buf.clear(); + cell.chunk_removed = false; + cell.incoming.clear(); + cell.outgoing.clear(); + + if let Some(chunk) = &mut cell.chunk { + chunk.update_post_client(); + } + + cell.chunk.is_some() || !cell.entities.is_empty() + }); + + instance.packet_buf.clear(); + } +} + +#[cfg(debug_assertions)] +fn check_instance_invariants(instances: Query<&Instance>, entities: Query<(), With>) { + for instance in &instances { + for (pos, cell) in &instance.partition { + for &id in &cell.entities { + assert!( + entities.get(id).is_ok(), + "instance contains an entity that does not exist at {pos:?}" + ); + } + } + } +} + /// An Instance represents a Minecraft world, which consist of [`Chunk`]s. /// It manages updating clients when chunks change, and caches chunk and entity /// update packets on a per-chunk basis. #[derive(Component)] pub struct Instance { - pub(crate) partition: FxHashMap, - pub(crate) info: InstanceInfo, + #[doc(hidden)] + pub partition: FxHashMap, + pub info: InstanceInfo, /// Packet data to send to all clients in this instance at the end of the /// tick. - pub(crate) packet_buf: Vec, + pub packet_buf: Vec, /// Scratch space for writing packets. scratch: Vec, } -pub(crate) struct InstanceInfo { +#[doc(hidden)] +pub struct InstanceInfo { dimension_type_name: Ident, section_count: usize, min_y: i32, @@ -71,23 +428,30 @@ pub(crate) struct InstanceInfo { filler_sky_light_arrays: Box<[LengthPrefixedArray]>, } +#[doc(hidden)] #[derive(Debug)] -pub(crate) struct PartitionCell { +pub struct PartitionCell { /// The chunk in this cell. - pub(crate) chunk: Option>, + #[doc(hidden)] + pub chunk: Option>, /// If `chunk` went from `Some` to `None` this tick. - pub(crate) chunk_removed: bool, + #[doc(hidden)] + pub chunk_removed: bool, /// Minecraft entities in this cell. - pub(crate) entities: BTreeSet, + #[doc(hidden)] + pub entities: BTreeSet, /// Minecraft entities that have entered the chunk this tick, paired with /// the cell position in this instance they came from. - pub(crate) incoming: Vec<(Entity, Option)>, + #[doc(hidden)] + pub incoming: Vec<(Entity, Option)>, /// Minecraft entities that have left the chunk this tick, paired with the /// cell position in this world they arrived at. - pub(crate) outgoing: Vec<(Entity, Option)>, + #[doc(hidden)] + pub outgoing: Vec<(Entity, Option)>, /// A cache of packets to send to all clients that are in view of this cell /// at the end of the tick. - pub(crate) packet_buf: Vec, + #[doc(hidden)] + pub packet_buf: Vec, } impl Instance { @@ -95,7 +459,7 @@ impl Instance { dimension_type_name: impl Into>, dimensions: &Query<&DimensionType>, biomes: &Query<&Biome>, - shared: &SharedServer, + server: &Server, ) -> Self { let dimension_type_name = dimension_type_name.into(); @@ -119,8 +483,8 @@ impl Instance { dimension_type_name, section_count: (dim.height / 16) as usize, min_y: dim.min_y, - biome_registry_len: biomes.iter().count(), - compression_threshold: shared.compression_threshold(), + biome_registry_len: biomes.iter().len(), + compression_threshold: server.compression_threshold(), filler_sky_light_mask: sky_light_mask.into(), filler_sky_light_arrays: vec![ LengthPrefixedArray([0xff; 2048]); @@ -137,7 +501,7 @@ impl Instance { #[doc(hidden)] pub fn new_unit_testing( dimension_type_name: impl Into>, - shared: &SharedServer, + server: &Server, ) -> Self { Self { partition: FxHashMap::default(), @@ -146,7 +510,7 @@ impl Instance { section_count: 24, min_y: -64, biome_registry_len: 1, - compression_threshold: shared.compression_threshold(), + compression_threshold: server.compression_threshold(), filler_sky_light_mask: vec![].into(), filler_sky_light_arrays: vec![].into(), }, @@ -418,8 +782,8 @@ impl Instance { &ParticleS2c { particle: Cow::Borrowed(particle), long_distance, - position: position.into(), - offset: offset.into().into(), + position, + offset: offset.into(), max_speed, count, }, @@ -444,7 +808,7 @@ impl Instance { &PlaySoundS2c { id: sound.to_id(), category, - position: (position * 8.0).as_ivec3().into(), + position: (position * 8.0).as_ivec3(), volume, pitch, seed: rand::random(), @@ -461,327 +825,7 @@ impl Instance { } } -pub(crate) struct InstancePlugin; - -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub(crate) struct WriteUpdatePacketsToInstancesSet; - -impl Plugin for InstancePlugin { - fn build(&self, app: &mut bevy_app::App) { - app.configure_set( - WriteUpdatePacketsToInstancesSet - .after(InitEntitiesSet) - .in_base_set(CoreSet::PostUpdate), - ) - .add_system( - update_entity_cell_positions - .before(WriteUpdatePacketsToInstancesSet) - .in_base_set(CoreSet::PostUpdate), - ) - .add_system(write_update_packets_to_instances.in_set(WriteUpdatePacketsToInstancesSet)) - .add_system( - update_instances_post_client - .after(FlushPacketsSet) - .in_base_set(CoreSet::PostUpdate), - ); - - #[cfg(debug_assertions)] - app.add_system(check_instance_invariants.in_base_set(CoreSet::PostUpdate)); - } -} - -/// Handles entities moving from one chunk to another. -fn update_entity_cell_positions( - entities: Query< - ( - Entity, - &Position, - &OldPosition, - &Location, - &OldLocation, - Option<&Despawned>, - ), - (With, Or<(Changed, With)>), - >, - mut instances: Query<&mut Instance>, -) { - for (entity, pos, old_pos, loc, old_loc, despawned) in &entities { - let pos = ChunkPos::at(pos.0.x, pos.0.z); - let old_pos = ChunkPos::at(old_pos.get().x, old_pos.get().z); - - if despawned.is_some() { - // Entity was deleted. Remove it from the chunk it was in, if it was in a chunk - // at all. - if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) { - if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) { - if old_cell.entities.remove(&entity) { - old_cell.outgoing.push((entity, None)); - } - } - } - } else if old_loc.get() != loc.0 { - // Entity changed the instance it is in. Remove it from old cell and - // insert it in the new cell. - - // TODO: skip marker entity? - - if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) { - if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) { - if old_cell.entities.remove(&entity) { - old_cell.outgoing.push((entity, None)); - } - } - } - - if let Ok(mut instance) = instances.get_mut(loc.0) { - match instance.partition.entry(pos) { - Entry::Occupied(oe) => { - let cell = oe.into_mut(); - if cell.entities.insert(entity) { - cell.incoming.push((entity, None)); - } - } - Entry::Vacant(ve) => { - ve.insert(PartitionCell { - chunk: None, - chunk_removed: false, - entities: BTreeSet::from([entity]), - incoming: vec![(entity, None)], - outgoing: vec![], - packet_buf: vec![], - }); - } - } - } - } else if pos != old_pos { - // Entity changed its chunk position without changing instances. Remove - // it from old cell and insert it in new cell. - - // TODO: skip marker entity? - - if let Ok(mut instance) = instances.get_mut(loc.0) { - if let Some(old_cell) = instance.partition.get_mut(&old_pos) { - if old_cell.entities.remove(&entity) { - old_cell.outgoing.push((entity, Some(pos))); - } - } - - match instance.partition.entry(pos) { - Entry::Occupied(oe) => { - let cell = oe.into_mut(); - if cell.entities.insert(entity) { - cell.incoming.push((entity, Some(old_pos))); - } - } - Entry::Vacant(ve) => { - ve.insert(PartitionCell { - chunk: None, - chunk_removed: false, - entities: BTreeSet::from([entity]), - incoming: vec![(entity, Some(old_pos))], - outgoing: vec![], - packet_buf: vec![], - }); - } - } - } - } else { - // The entity didn't change its chunk position so there is nothing - // we need to do. - } - } -} - -/// Writes update packets from entities and chunks into each cell's packet -/// buffer. -fn write_update_packets_to_instances( - mut instances: Query<&mut Instance>, - mut entities: Query, Without)>, - server: Res, -) { - let mut scratch_1 = vec![]; - let mut scratch_2 = vec![]; - - for instance in &mut instances { - let instance = instance.into_inner(); - - for (&pos, cell) in &mut instance.partition { - // Cache chunk update packets into the packet buffer of this cell. - if let Some(chunk) = &mut cell.chunk { - let writer = PacketWriter::new( - &mut cell.packet_buf, - server.compression_threshold(), - &mut scratch_2, - ); - - chunk.write_update_packets(writer, &mut scratch_1, pos, &instance.info); - - chunk.clear_viewed(); - } - - // Cache entity update packets into the packet buffer of this cell. - for &entity in &cell.entities { - let mut entity = entities - .get_mut(entity) - .expect("missing entity in partition cell"); - - let start = cell.packet_buf.len(); - - let writer = PacketWriter::new( - &mut cell.packet_buf, - server.compression_threshold(), - &mut scratch_2, - ); - - entity.write_update_packets(writer); - - let end = cell.packet_buf.len(); - - entity.packet_byte_range.0 = start..end; - } - } - } -} - -#[derive(WorldQuery)] -#[world_query(mutable)] -struct UpdateEntityQuery { - id: &'static EntityId, - pos: &'static Position, - old_pos: &'static OldPosition, - loc: &'static Location, - old_loc: &'static OldLocation, - look: Ref<'static, Look>, - head_yaw: Ref<'static, HeadYaw>, - on_ground: &'static OnGround, - velocity: Ref<'static, Velocity>, - tracked_data: &'static TrackedData, - statuses: &'static EntityStatuses, - animations: &'static EntityAnimations, - packet_byte_range: &'static mut PacketByteRange, -} - -impl UpdateEntityQueryItem<'_> { - fn write_update_packets(&self, mut writer: impl WritePacket) { - // TODO: @RJ I saw you're using UpdateEntityPosition and UpdateEntityRotation sometimes. These two packets are actually broken on the client and will erase previous position/rotation https://bugs.mojang.com/browse/MC-255263 -Moulberry - - let entity_id = VarInt(self.id.get()); - - let position_delta = self.pos.0 - self.old_pos.get(); - let needs_teleport = position_delta.abs().max_element() >= 8.0; - let changed_position = self.pos.0 != self.old_pos.get(); - - if changed_position && !needs_teleport && self.look.is_changed() { - writer.write_packet(&RotateAndMoveRelative { - entity_id, - delta: (position_delta * 4096.0).to_array().map(|v| v as i16), - yaw: ByteAngle::from_degrees(self.look.yaw), - pitch: ByteAngle::from_degrees(self.look.pitch), - on_ground: self.on_ground.0, - }); - } else { - if changed_position && !needs_teleport { - writer.write_packet(&MoveRelative { - entity_id, - delta: (position_delta * 4096.0).to_array().map(|v| v as i16), - on_ground: self.on_ground.0, - }); - } - - if self.look.is_changed() { - writer.write_packet(&Rotate { - entity_id, - yaw: ByteAngle::from_degrees(self.look.yaw), - pitch: ByteAngle::from_degrees(self.look.pitch), - on_ground: self.on_ground.0, - }); - } - } - - if needs_teleport { - writer.write_packet(&EntityPositionS2c { - entity_id, - position: self.pos.0.to_array(), - yaw: ByteAngle::from_degrees(self.look.yaw), - pitch: ByteAngle::from_degrees(self.look.pitch), - on_ground: self.on_ground.0, - }); - } - - if self.velocity.is_changed() { - writer.write_packet(&EntityVelocityUpdateS2c { - entity_id, - velocity: velocity_to_packet_units(self.velocity.0), - }); - } - - if self.head_yaw.is_changed() { - writer.write_packet(&EntitySetHeadYawS2c { - entity_id, - head_yaw: ByteAngle::from_degrees(self.head_yaw.0), - }); - } - - if let Some(update_data) = self.tracked_data.update_data() { - writer.write_packet(&EntityTrackerUpdateS2c { - entity_id, - metadata: update_data.into(), - }); - } - - if self.statuses.0 != 0 { - for i in 0..mem::size_of_val(self.statuses) { - if (self.statuses.0 >> i) & 1 == 1 { - writer.write_packet(&EntityStatusS2c { - entity_id: entity_id.0, - entity_status: i as u8, - }); - } - } - } - - if self.animations.0 != 0 { - for i in 0..mem::size_of_val(self.animations) { - if (self.animations.0 >> i) & 1 == 1 { - writer.write_packet(&EntityAnimationS2c { - entity_id, - animation: i as u8, - }); - } - } - } - } -} - -fn update_instances_post_client(mut instances: Query<&mut Instance>) { - for mut instance in &mut instances { - instance.partition.retain(|_, cell| { - cell.packet_buf.clear(); - cell.chunk_removed = false; - cell.incoming.clear(); - cell.outgoing.clear(); - - if let Some(chunk) = &mut cell.chunk { - chunk.update_post_client(); - } - - cell.chunk.is_some() || !cell.entities.is_empty() - }); - - instance.packet_buf.clear(); - } -} - -#[cfg(debug_assertions)] -fn check_instance_invariants(instances: Query<&Instance>, entities: Query<(), With>) { - for instance in &instances { - for (pos, cell) in &instance.partition { - for &id in &cell.entities { - assert!( - entities.get(id).is_ok(), - "instance contains an entity that does not exist at {pos:?}" - ); - } - } - } +/// Returns the minimum number of bits needed to represent the integer `n`. +const fn bit_width(n: usize) -> usize { + (usize::BITS - n.leading_zeros()) as _ } diff --git a/crates/valence/src/instance/paletted_container.rs b/crates/valence_instance/src/paletted_container.rs similarity index 92% rename from crates/valence/src/instance/paletted_container.rs rename to crates/valence_instance/src/paletted_container.rs index 6e4c6e2..929d21a 100644 --- a/crates/valence/src/instance/paletted_container.rs +++ b/crates/valence_instance/src/paletted_container.rs @@ -2,21 +2,22 @@ use std::array; use std::io::Write; use arrayvec::ArrayVec; -use valence_protocol::var_int::VarInt; -use valence_protocol::Encode; +use num_integer::div_ceil; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::Encode; -use crate::util::bit_width; +use crate::bit_width; /// `HALF_LEN` must be equal to `ceil(LEN / 2)`. #[derive(Clone, Debug)] -pub enum PalettedContainer { +pub(crate) enum PalettedContainer { Single(T), Indirect(Box>), Direct(Box<[T; LEN]>), } #[derive(Clone, Debug)] -pub struct Indirect { +pub(crate) struct Indirect { /// Each element is a unique instance of `T`. The length of the palette is /// always ≥2. palette: ArrayVec, @@ -27,18 +28,18 @@ pub struct Indirect { impl PalettedContainer { - pub fn new() -> Self { - assert_eq!(num::Integer::div_ceil(&LEN, &2), HALF_LEN); + pub(crate) fn new() -> Self { + assert_eq!(div_ceil(LEN, 2), HALF_LEN); assert_ne!(LEN, 0); Self::Single(T::default()) } - pub fn fill(&mut self, val: T) { + pub(crate) fn fill(&mut self, val: T) { *self = Self::Single(val) } - pub fn get(&self, idx: usize) -> T { + pub(crate) fn get(&self, idx: usize) -> T { debug_assert!(idx < LEN); match self { @@ -48,7 +49,7 @@ impl } } - pub fn set(&mut self, idx: usize, val: T) -> T { + pub(crate) fn set(&mut self, idx: usize, val: T) -> T { debug_assert!(idx < LEN); match self { @@ -86,7 +87,7 @@ impl } } - pub fn optimize(&mut self) { + pub(crate) fn optimize(&mut self) { match self { Self::Single(_) => {} Self::Indirect(ind) => { @@ -141,7 +142,7 @@ impl /// - **`direct_bits`**: The minimum number of bits required to represent /// all instances of the element type. If `N` is the total number of /// possible values, then `DIRECT_BITS` is `floor(log2(N - 1)) + 1`. - pub fn encode_mc_format( + pub(crate) fn encode_mc_format( &self, mut writer: W, mut to_bits: F, @@ -235,12 +236,12 @@ impl Default } impl Indirect { - pub fn get(&self, idx: usize) -> T { + pub(crate) fn get(&self, idx: usize) -> T { let palette_idx = self.indices[idx / 2] >> (idx % 2 * 4) & 0b1111; self.palette[palette_idx as usize] } - pub fn set(&mut self, idx: usize, val: T) -> Option { + pub(crate) fn set(&mut self, idx: usize, val: T) -> Option { let palette_idx = if let Some(i) = self.palette.iter().position(|v| *v == val) { i } else { @@ -256,9 +257,10 @@ impl Indirect usize { let vals_per_u64 = 64 / bits_per_val; - num::Integer::div_ceil(&vals_count, &vals_per_u64) + div_ceil(vals_count, vals_per_u64) } #[inline] diff --git a/crates/valence_inventory/Cargo.toml b/crates/valence_inventory/Cargo.toml new file mode 100644 index 0000000..4e0a8d7 --- /dev/null +++ b/crates/valence_inventory/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "valence_inventory" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +tracing.workspace = true +valence_client.workspace = true +valence_core.workspace = true diff --git a/crates/valence_inventory/README.md b/crates/valence_inventory/README.md new file mode 100644 index 0000000..ae22172 --- /dev/null +++ b/crates/valence_inventory/README.md @@ -0,0 +1,31 @@ +# valence_inventory + +The inventory system. + +This module contains the systems and components needed to handle +inventories. By default, clients will have a player inventory attached to +them. + +# Components + +- [`Inventory`]: The inventory component. This is the thing that holds + items. +- [`OpenInventory`]: The component that is attached to clients when they + have an inventory open. + +# Examples + +An example system that will let you access all player's inventories: + +``` +# use bevy_ecs::prelude::*; +# use valence_inventory::*; +# use valence_client::Client; +fn system(clients: Query<(&Client, &Inventory)>) {} +``` + +### See also + +Examples related to inventories in the `valence/examples/` directory: +- `building` +- `chest` diff --git a/crates/valence/src/inventory.rs b/crates/valence_inventory/src/lib.rs similarity index 53% rename from crates/valence/src/inventory.rs rename to crates/valence_inventory/src/lib.rs index 71d6791..640b647 100644 --- a/crates/valence/src/inventory.rs +++ b/crates/valence_inventory/src/lib.rs @@ -1,63 +1,60 @@ -//! The inventory system. -//! -//! This module contains the systems and components needed to handle -//! inventories. By default, clients will have a player inventory attached to -//! them. -//! -//! # Components -//! -//! - [`Inventory`]: The inventory component. This is the thing that holds -//! items. -//! - [`OpenInventory`]: The component that is attached to clients when they -//! have an inventory open. -//! -//! # Examples -//! -//! An example system that will let you access all player's inventories: -//! -//! ```rust -//! # use valence::prelude::*; -//! fn system(mut clients: Query<(&Client, &Inventory)>) {} -//! ``` -//! -//! ### See also -//! -//! Examples related to inventories in the `examples/` directory: -//! - `building` -//! - `chest` +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] use std::borrow::Cow; use std::iter::FusedIterator; +use std::num::Wrapping; use std::ops::Range; use bevy_app::prelude::*; use bevy_ecs::prelude::*; use tracing::{debug, warn}; -use valence_protocol::item::ItemStack; -use valence_protocol::packet::c2s::play::click_slot::{ClickMode, Slot}; -use valence_protocol::packet::c2s::play::{ +use valence_client::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent, RunEventLoopSet}; +use valence_client::{Client, FlushPacketsSet, SpawnClientsSet}; +use valence_core::game_mode::GameMode; +use valence_core::item::ItemStack; +use valence_core::packet::c2s::play::click_slot::{ClickMode, Slot}; +use valence_core::packet::c2s::play::{ ClickSlotC2s, CloseHandledScreenC2s, CreativeInventoryActionC2s, PlayerActionC2s, UpdateSelectedSlotC2s, }; -use valence_protocol::packet::s2c::play::{ +use valence_core::packet::encode::WritePacket; +use valence_core::packet::s2c::play::open_screen::WindowType; +use valence_core::packet::s2c::play::{ CloseScreenS2c, InventoryS2c, OpenScreenS2c, ScreenHandlerSlotUpdateS2c, }; -use valence_protocol::text::Text; -use valence_protocol::types::WindowType; -use valence_protocol::var_int::VarInt; - -use crate::client::{Client, ClientInventoryState, CursorItem, FlushPacketsSet}; -use crate::component::GameMode; -use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; -use crate::packet::WritePacket; +use valence_core::packet::var_int::VarInt; +use valence_core::text::Text; mod validate; -pub(crate) struct InventoryPlugin; +pub struct InventoryPlugin; impl Plugin for InventoryPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_systems( + app.add_system( + init_new_client_inventories + .in_base_set(CoreSet::PreUpdate) + .after(SpawnClientsSet) + .before(RunEventLoopSet), + ) + .add_systems( ( update_open_inventories, update_client_on_close_inventory.after(update_open_inventories), @@ -95,7 +92,8 @@ pub struct Inventory { kind: InventoryKind, slots: Box<[Option]>, /// Contains a set bit for each modified slot in `slots`. - changed: u64, + #[doc(hidden)] + pub changed: u64, } impl Inventory { @@ -126,7 +124,8 @@ impl Inventory { /// See also [`Inventory::replace_slot`]. /// /// ``` - /// # use valence::prelude::*; + /// # use valence_inventory::*; + /// # use valence_core::item::{ItemStack, ItemKind}; /// let mut inv = Inventory::new(InventoryKind::Generic9x1); /// inv.set_slot(0, ItemStack::new(ItemKind::Diamond, 1, None)); /// assert_eq!(inv.slot(0).unwrap().item, ItemKind::Diamond); @@ -143,7 +142,8 @@ impl Inventory { /// See also [`Inventory::set_slot`]. /// /// ``` - /// # use valence::prelude::*; + /// # use valence_inventory::*; + /// # use valence_core::item::{ItemStack, ItemKind}; /// let mut inv = Inventory::new(InventoryKind::Generic9x1); /// inv.set_slot(0, ItemStack::new(ItemKind::Diamond, 1, None)); /// let old = inv.replace_slot(0, ItemStack::new(ItemKind::IronIngot, 1, None)); @@ -172,7 +172,8 @@ impl Inventory { /// happens. /// /// ``` - /// # use valence::prelude::*; + /// # use valence_inventory::*; + /// # use valence_core::item::{ItemStack, ItemKind}; /// let mut inv = Inventory::new(InventoryKind::Generic9x1); /// inv.set_slot(0, ItemStack::new(ItemKind::Diamond, 1, None)); /// assert_eq!(inv.slot(1), None); @@ -206,7 +207,8 @@ impl Inventory { /// clamped to this range. If the slot is empty, nothing happens. /// /// ``` - /// # use valence::prelude::*; + /// # use valence_inventory::*; + /// # use valence_core::item::{ItemStack, ItemKind}; /// let mut inv = Inventory::new(InventoryKind::Generic9x1); /// inv.set_slot(0, ItemStack::new(ItemKind::Diamond, 1, None)); /// inv.set_slot_amount(0, 64); @@ -246,8 +248,9 @@ impl Inventory { /// The text displayed on the inventory's title bar. /// /// ``` - /// # use valence::inventory::{Inventory, InventoryKind}; - /// # use valence_protocol::text::Text; + /// # use valence_inventory::*; + /// # use valence_core::item::{ItemStack, ItemKind}; + /// # use valence_core::text::Text; /// let inv = Inventory::with_title(InventoryKind::Generic9x3, "Box of Holding"); /// assert_eq!(inv.title(), &Text::from("Box of Holding")); /// ``` @@ -260,7 +263,7 @@ impl Inventory { /// To get the old title, use [`Inventory::replace_title`]. /// /// ``` - /// # use valence::inventory::{Inventory, InventoryKind}; + /// # use valence_inventory::*; /// let mut inv = Inventory::new(InventoryKind::Generic9x3); /// inv.set_title("Box of Holding"); /// ``` @@ -285,7 +288,8 @@ impl Inventory { /// no empty slots in the range. /// /// ``` - /// # use valence::prelude::*; + /// # use valence_inventory::*; + /// # use valence_core::item::*; /// let mut inv = Inventory::new(InventoryKind::Generic9x1); /// inv.set_slot(0, ItemStack::new(ItemKind::Diamond, 1, None)); /// inv.set_slot(2, ItemStack::new(ItemKind::GoldIngot, 1, None)); @@ -308,7 +312,8 @@ impl Inventory { /// Returns the first empty slot in the inventory, or `None` if there are no /// empty slots. /// ``` - /// # use valence::prelude::*; + /// # use valence_inventory::*; + /// # use valence_core::item::*; /// let mut inv = Inventory::new(InventoryKind::Generic9x1); /// inv.set_slot(0, ItemStack::new(ItemKind::Diamond, 1, None)); /// inv.set_slot(2, ItemStack::new(ItemKind::GoldIngot, 1, None)); @@ -320,6 +325,44 @@ impl Inventory { } } +/// Miscellaneous inventory data. +#[derive(Component, Debug)] +pub struct ClientInventoryState { + /// The current window ID. Incremented when inventories are opened. + window_id: u8, + state_id: Wrapping, + /// Tracks what slots have been changed by this client in this tick, so we + /// don't need to send updates for them. + slots_changed: u64, + /// Whether the client has updated the cursor item in this tick. This is not + /// on the `CursorItem` component to make maintaining accurate change + /// detection for end users easier. + client_updated_cursor_item: bool, + // TODO: make this a separate modifiable component. + held_item_slot: u16, +} + +impl ClientInventoryState { + pub fn held_item_slot(&self) -> u16 { + self.held_item_slot + } + + #[doc(hidden)] + pub fn window_id(&self) -> u8 { + self.window_id + } + + #[doc(hidden)] + pub fn state_id(&self) -> Wrapping { + self.state_id + } +} + +/// The item stack that the client thinks it's holding under the mouse +/// cursor. +#[derive(Component, Clone, PartialEq, Default, Debug)] +pub struct CursorItem(pub Option); + /// Used to indicate that the client with this component is currently viewing /// an inventory. #[derive(Component, Clone, Debug)] @@ -345,11 +388,14 @@ impl OpenInventory { /// This is a read-only version of [`InventoryWindowMut`]. /// /// ``` -/// # use valence::prelude::*; +/// # use valence_inventory::*; +/// # use valence_core::item::*; /// let mut player_inventory = Inventory::new(InventoryKind::Player); /// player_inventory.set_slot(36, ItemStack::new(ItemKind::Diamond, 1, None)); +/// /// let target_inventory = Inventory::new(InventoryKind::Generic9x3); /// let window = InventoryWindow::new(&player_inventory, Some(&target_inventory)); +/// /// assert_eq!( /// window.slot(54), /// Some(&ItemStack::new(ItemKind::Diamond, 1, None)) @@ -398,11 +444,14 @@ impl<'a> InventoryWindow<'a> { /// This is a writable version of [`InventoryWindow`]. /// /// ``` -/// # use valence::prelude::*; +/// # use valence_inventory::*; +/// # use valence_core::item::*; /// let mut player_inventory = Inventory::new(InventoryKind::Player); /// let mut target_inventory = Inventory::new(InventoryKind::Generic9x3); /// let mut window = InventoryWindowMut::new(&mut player_inventory, Some(&mut target_inventory)); +/// /// window.set_slot(54, ItemStack::new(ItemKind::Diamond, 1, None)); +/// /// assert_eq!( /// player_inventory.slot(36), /// Some(&ItemStack::new(ItemKind::Diamond, 1, None)) @@ -474,6 +523,24 @@ impl<'a> InventoryWindowMut<'a> { } } +/// Attach the necessary inventory components to new clients. +fn init_new_client_inventories(clients: Query>, mut commands: Commands) { + for entity in &clients { + commands.entity(entity).insert(( + Inventory::new(InventoryKind::Player), + CursorItem(None), + ClientInventoryState { + window_id: 0, + state_id: Wrapping(0), + slots_changed: 0, + client_updated_cursor_item: false, + // First slot of the hotbar. + held_item_slot: 36, + }, + )); + } +} + /// Send updates for each client's player inventory. fn update_player_inventories( mut query: Query< @@ -764,7 +831,8 @@ fn handle_click_slot( let entire_stack = pkt.button == 1; - // Needs to open the inventory for if the player is dropping an item while having an inventory open. + // Needs to open the inventory for if the player is dropping an item while + // having an inventory open. if let Some(open_inventory) = open_inventory { // The player is interacting with an inventory that is open. @@ -794,8 +862,6 @@ fn handle_click_slot( // The player is dropping an item from another inventory. if let Some(stack) = target_inventory.slot(pkt.slot_idx as u16) { - // TODO: is the use of `replace_slot` here causing unnecessary packets to be - // sent? let dropped = if entire_stack || stack.count() == 1 { target_inventory.replace_slot(pkt.slot_idx as u16, None) } else { @@ -821,8 +887,6 @@ fn handle_click_slot( let slot_id = convert_to_player_slot_id(target_inventory.kind, pkt.slot_idx as u16); if let Some(stack) = client_inv.slot(slot_id) { - // TODO: is the use of `replace_slot` here causing unnecessary packets to be - // sent? let dropped = if entire_stack || stack.count() == 1 { client_inv.replace_slot(slot_id, None) } else { @@ -844,10 +908,9 @@ fn handle_click_slot( } } } else { - // The player has no inventory open and is dropping an item from their inventory. + // The player has no inventory open and is dropping an item from their + // inventory. if let Some(stack) = client_inv.slot(pkt.slot_idx as u16) { - // TODO: is the use of `replace_slot` here causing unnecessary packets to be - // sent? let dropped = if entire_stack || stack.count() == 1 { client_inv.replace_slot(pkt.slot_idx as u16, None) } else { @@ -982,7 +1045,7 @@ fn handle_player_actions( ) { for packet in packets.iter() { if let Some(pkt) = packet.decode::() { - use valence_protocol::packet::c2s::play::player_action::Action; + use valence_core::packet::c2s::play::player_action::Action; match pkt.action { Action::DropAllItems => { @@ -1129,7 +1192,8 @@ fn handle_update_selected_slot( /// Convert a slot that is outside a target inventory's range to a slot that is /// inside the player's inventory. -fn convert_to_player_slot_id(target_kind: InventoryKind, slot_id: u16) -> u16 { +#[doc(hidden)] +pub fn convert_to_player_slot_id(target_kind: InventoryKind, slot_id: u16) -> u16 { // the first slot in the player's general inventory let offset = target_kind.slot_count() as u16; slot_id - offset + 9 @@ -1281,16 +1345,8 @@ impl Default for InventorySettings { } #[cfg(test)] -mod test { - use bevy_app::App; - use valence_protocol::item::ItemKind; - use valence_protocol::packet::c2s::play::click_slot::{ClickMode, Slot}; - use valence_protocol::packet::c2s::play::ClickSlotC2s; - use valence_protocol::packet::S2cPlayPacket; - +mod tests { use super::*; - use crate::unit_test::util::scenario_single_client; - use crate::{assert_packet_count, assert_packet_order}; #[test] fn test_convert_to_player_slot() { @@ -1306,1010 +1362,4 @@ mod test { assert_eq!(convert_hotbar_slot_id(4), 40); assert_eq!(convert_hotbar_slot_id(8), 44); } - - #[test] - fn test_should_open_inventory() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - let inventory = Inventory::new(InventoryKind::Generic3x3); - let inventory_ent = app.world.spawn(inventory).id(); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Open the inventory. - let open_inventory = OpenInventory::new(inventory_ent); - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .insert(open_inventory); - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - - assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_)); - assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); - assert_packet_order!( - sent_packets, - S2cPlayPacket::OpenScreenS2c(_), - S2cPlayPacket::InventoryS2c(_) - ); - } - - #[test] - fn test_should_close_inventory() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - let inventory = Inventory::new(InventoryKind::Generic3x3); - let inventory_ent = app.world.spawn(inventory).id(); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Open the inventory. - let open_inventory = OpenInventory::new(inventory_ent); - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .insert(open_inventory); - - app.update(); - client_helper.clear_sent(); - - // Close the inventory. - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .remove::(); - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - - assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_)); - } - - #[test] - fn test_should_remove_invalid_open_inventory() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - let inventory = Inventory::new(InventoryKind::Generic3x3); - let inventory_ent = app.world.spawn(inventory).id(); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Open the inventory. - let open_inventory = OpenInventory::new(inventory_ent); - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .insert(open_inventory); - - app.update(); - client_helper.clear_sent(); - - // Remove the inventory. - app.world.despawn(inventory_ent); - - app.update(); - - // Make assertions - assert!(app.world.get::(client_ent).is_none()); - let sent_packets = client_helper.collect_sent(); - assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_)); - } - - #[test] - fn test_should_modify_player_inventory_click_slot() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory for client"); - inventory.set_slot(20, ItemStack::new(ItemKind::Diamond, 2, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Make the client click the slot and pick up the item. - let state_id = app - .world - .get::(client_ent) - .unwrap() - .state_id; - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id: 0, - button: 0, - mode: valence_protocol::packet::c2s::play::click_slot::ClickMode::Click, - state_id: VarInt(state_id.0), - slot_idx: 20, - slot_changes: vec![valence_protocol::packet::c2s::play::click_slot::Slot { - idx: 20, - item: None, - }], - carried_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), - }); - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - - // because the inventory was changed as a result of the client's click, the - // server should not send any packets to the client because the client - // already knows about the change. - assert_packet_count!( - sent_packets, - 0, - S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) - ); - let inventory = app - .world - .get::(client_ent) - .expect("could not find inventory for client"); - assert_eq!(inventory.slot(20), None); - let cursor_item = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!( - cursor_item.0, - Some(ItemStack::new(ItemKind::Diamond, 2, None)) - ); - } - - #[test] - fn test_should_modify_player_inventory_server_side() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory for client"); - inventory.set_slot(20, ItemStack::new(ItemKind::Diamond, 2, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Modify the inventory. - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory for client"); - inventory.set_slot(21, ItemStack::new(ItemKind::IronIngot, 1, None)); - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - // because the inventory was modified server side, the client needs to be - // updated with the change. - assert_packet_count!( - sent_packets, - 1, - S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) - ); - } - - #[test] - fn test_should_sync_entire_player_inventory() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory for client"); - inventory.changed = u64::MAX; - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); - } - - fn set_up_open_inventory(app: &mut App, client_ent: Entity) -> Entity { - let inventory = Inventory::new(InventoryKind::Generic9x3); - let inventory_ent = app.world.spawn(inventory).id(); - - // Open the inventory. - let open_inventory = OpenInventory::new(inventory_ent); - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .insert(open_inventory); - - inventory_ent - } - - #[test] - fn test_should_modify_open_inventory_click_slot() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let inventory_ent = set_up_open_inventory(&mut app, client_ent); - let mut inventory = app - .world - .get_mut::(inventory_ent) - .expect("could not find inventory for client"); - inventory.set_slot(20, ItemStack::new(ItemKind::Diamond, 2, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Make the client click the slot and pick up the item. - let inv_state = app.world.get::(client_ent).unwrap(); - let state_id = inv_state.state_id; - let window_id = inv_state.window_id; - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id, - button: 0, - mode: valence_protocol::packet::c2s::play::click_slot::ClickMode::Click, - state_id: VarInt(state_id.0), - slot_idx: 20, - slot_changes: vec![valence_protocol::packet::c2s::play::click_slot::Slot { - idx: 20, - item: None, - }], - carried_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), - }); - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - - // because the inventory was modified as a result of the client's click, the - // server should not send any packets to the client because the client - // already knows about the change. - assert_packet_count!( - sent_packets, - 0, - S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) - ); - let inventory = app - .world - .get::(inventory_ent) - .expect("could not find inventory"); - assert_eq!(inventory.slot(20), None); - let cursor_item = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!( - cursor_item.0, - Some(ItemStack::new(ItemKind::Diamond, 2, None)) - ); - } - - #[test] - fn test_should_modify_open_inventory_server_side() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let inventory_ent = set_up_open_inventory(&mut app, client_ent); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - // Modify the inventory. - let mut inventory = app - .world - .get_mut::(inventory_ent) - .expect("could not find inventory for client"); - inventory.set_slot(5, ItemStack::new(ItemKind::IronIngot, 1, None)); - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - - // because the inventory was modified server side, the client needs to be - // updated with the change. - assert_packet_count!( - sent_packets, - 1, - S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) - ); - let inventory = app - .world - .get::(inventory_ent) - .expect("could not find inventory for client"); - assert_eq!( - inventory.slot(5), - Some(&ItemStack::new(ItemKind::IronIngot, 1, None)) - ); - } - - #[test] - fn test_should_sync_entire_open_inventory() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let inventory_ent = set_up_open_inventory(&mut app, client_ent); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - let mut inventory = app - .world - .get_mut::(inventory_ent) - .expect("could not find inventory"); - inventory.changed = u64::MAX; - - app.update(); - - // Make assertions - let sent_packets = client_helper.collect_sent(); - assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); - } - - #[test] - fn test_set_creative_mode_slot_handling() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut game_mode = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - *game_mode.as_mut() = GameMode::Creative; - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send( - &valence_protocol::packet::c2s::play::CreativeInventoryActionC2s { - slot: 36, - clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), - }, - ); - - app.update(); - - // Make assertions - let inventory = app - .world - .get::(client_ent) - .expect("could not find inventory for client"); - assert_eq!( - inventory.slot(36), - Some(&ItemStack::new(ItemKind::Diamond, 2, None)) - ); - } - - #[test] - fn test_ignore_set_creative_mode_slot_if_not_creative() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut game_mode = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - *game_mode.as_mut() = GameMode::Survival; - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send( - &valence_protocol::packet::c2s::play::CreativeInventoryActionC2s { - slot: 36, - clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), - }, - ); - - app.update(); - - // Make assertions - let inventory = app - .world - .get::(client_ent) - .expect("could not find inventory for client"); - assert_eq!(inventory.slot(36), None); - } - - #[test] - fn test_window_id_increments() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let inventory = Inventory::new(InventoryKind::Generic9x3); - let inventory_ent = app.world.spawn(inventory).id(); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - for _ in 0..3 { - let open_inventory = OpenInventory::new(inventory_ent); - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .insert(open_inventory); - - app.update(); - - app.world - .get_entity_mut(client_ent) - .expect("could not find client") - .remove::(); - - app.update(); - } - - // Make assertions - let inv_state = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!(inv_state.window_id, 3); - } - - #[test] - fn test_should_handle_set_held_item() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send(&valence_protocol::packet::c2s::play::UpdateSelectedSlotC2s { slot: 4 }); - - app.update(); - - // Make assertions - let inv_state = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!(inv_state.held_item_slot, 40); - } - - #[test] - fn should_not_increment_state_id_on_cursor_item_change() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - let inv_state = app - .world - .get::(client_ent) - .expect("could not find client"); - let expected_state_id = inv_state.state_id.0; - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - let mut cursor_item = app.world.get_mut::(client_ent).unwrap(); - cursor_item.0 = Some(ItemStack::new(ItemKind::Diamond, 2, None)); - - app.update(); - - // Make assertions - let inv_state = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!( - inv_state.state_id.0, expected_state_id, - "state id should not have changed" - ); - } - - mod dropping_items { - use valence_protocol::block_pos::BlockPos; - use valence_protocol::packet::c2s::play::click_slot::{ClickMode, Slot}; - use valence_protocol::packet::c2s::play::player_action::Action; - use valence_protocol::types::Direction; - - use super::*; - - #[test] - fn should_drop_item_player_action() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory"); - inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 3, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send(&valence_protocol::packet::c2s::play::PlayerActionC2s { - action: Action::DropItem, - position: BlockPos::new(0, 0, 0), - direction: Direction::Down, - sequence: VarInt(0), - }); - - app.update(); - - // Make assertions - let inventory = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!( - inventory.slot(36), - Some(&ItemStack::new(ItemKind::IronIngot, 2, None)) - ); - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!(events[0].from_slot, Some(36)); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 1, None) - ); - - let sent_packets = client_helper.collect_sent(); - assert_packet_count!( - sent_packets, - 0, - S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_) - ); - } - - #[test] - fn should_drop_item_stack_player_action() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory"); - inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 32, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send(&valence_protocol::packet::c2s::play::PlayerActionC2s { - action: Action::DropAllItems, - position: BlockPos::new(0, 0, 0), - direction: Direction::Down, - sequence: VarInt(0), - }); - - app.update(); - - // Make assertions - let inv_state = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!(inv_state.held_item_slot, 36); - let inventory = app - .world - .get::(client_ent) - .expect("could not find inventory"); - assert_eq!(inventory.slot(36), None); - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!(events[0].from_slot, Some(36)); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 32, None) - ); - } - - #[test] - fn should_drop_item_stack_set_creative_mode_slot() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - app.world.entity_mut(client_ent).insert(GameMode::Creative); - - client_helper.send( - &valence_protocol::packet::c2s::play::CreativeInventoryActionC2s { - slot: -1, - clicked_item: Some(ItemStack::new(ItemKind::IronIngot, 32, None)), - }, - ); - - app.update(); - - // Make assertions - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events") - .iter_current_update_events() - .collect::>(); - - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!(events[0].from_slot, None); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 32, None) - ); - } - - #[test] - fn should_drop_item_stack_click_container_outside() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut cursor_item = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - cursor_item.0 = Some(ItemStack::new(ItemKind::IronIngot, 32, None)); - let inv_state = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - let state_id = inv_state.state_id.0; - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id: 0, - slot_idx: -999, - button: 0, - mode: ClickMode::Click, - state_id: VarInt(state_id), - slot_changes: vec![], - carried_item: None, - }); - - app.update(); - - // Make assertions - let cursor_item = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!(cursor_item.0, None); - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!(events[0].from_slot, None); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 32, None) - ); - } - - #[test] - fn should_drop_item_click_container_with_dropkey_single() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let inv_state = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - let state_id = inv_state.state_id.0; - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory"); - inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id: 0, - slot_idx: 40, - button: 0, - mode: ClickMode::DropKey, - state_id: VarInt(state_id), - slot_changes: vec![Slot { - idx: 40, - item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)), - }], - carried_item: None, - }); - - app.update(); - - // Make assertions - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!(events[0].from_slot, Some(40)); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 1, None) - ); - } - - #[test] - fn should_drop_item_stack_click_container_with_dropkey() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let inv_state = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - let state_id = inv_state.state_id.0; - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory"); - inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id: 0, - slot_idx: 40, - button: 1, // pressing control - mode: ClickMode::DropKey, - state_id: VarInt(state_id), - slot_changes: vec![Slot { - idx: 40, - item: None, - }], - carried_item: None, - }); - - app.update(); - - // Make assertions - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!(events[0].from_slot, Some(40)); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 32, None) - ); - } - - #[test] - fn should_drop_item_player_open_inventory_with_dropkey() { - // The item should be dropped successfully, if the player has an inventory open and the slot id points to his inventory. - - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory"); - inventory.set_slot( - convert_to_player_slot_id(InventoryKind::Generic9x3, 50), - ItemStack::new(ItemKind::IronIngot, 32, None), - ); - let _inventory_ent = set_up_open_inventory(&mut app, client_ent); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - let inv_state = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - - let state_id = inv_state.state_id.0; - let window_id = inv_state.window_id; - - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id, - slot_idx: 50, - button: 0, // not pressing control - mode: ClickMode::DropKey, - state_id: VarInt(state_id), - slot_changes: vec![Slot { - idx: 50, - item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)), - }], - carried_item: None, - }); - - app.update(); - - // Make assertions - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let player_inventory = app - .world - .get::(client_ent) - .expect("could not find inventory"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!( - events[0].from_slot, - Some(convert_to_player_slot_id(InventoryKind::Generic9x3, 50)) - ); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 1, None) - ); - // Also make sure that the player inventory was updated correctly. - let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50); - assert_eq!( - player_inventory.slot(expected_player_slot_id), - Some(&ItemStack::new(ItemKind::IronIngot, 31, None)) - ); - } - } - - #[test] - fn should_drop_item_stack_player_open_inventory_with_dropkey() { - // The item stack should be dropped successfully, if the player has an inventory open and the slot id points to his inventory. - - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - let mut inventory = app - .world - .get_mut::(client_ent) - .expect("could not find inventory"); - inventory.set_slot( - convert_to_player_slot_id(InventoryKind::Generic9x3, 50), - ItemStack::new(ItemKind::IronIngot, 32, None), - ); - let _inventory_ent = set_up_open_inventory(&mut app, client_ent); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - let inv_state = app - .world - .get_mut::(client_ent) - .expect("could not find client"); - - let state_id = inv_state.state_id.0; - let window_id = inv_state.window_id; - - client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s { - window_id, - slot_idx: 50, - button: 1, // pressing control, the whole stack is dropped - mode: ClickMode::DropKey, - state_id: VarInt(state_id), - slot_changes: vec![Slot { - idx: 50, - item: None, - }], - carried_item: None, - }); - - app.update(); - - // Make assertions - let events = app - .world - .get_resource::>() - .expect("expected drop item stack events"); - let player_inventory = app - .world - .get::(client_ent) - .expect("could not find inventory"); - let events = events.iter_current_update_events().collect::>(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].client, client_ent); - assert_eq!( - events[0].from_slot, - Some(convert_to_player_slot_id(InventoryKind::Generic9x3, 50)) - ); - assert_eq!( - events[0].stack, - ItemStack::new(ItemKind::IronIngot, 32, None) - ); - // Also make sure that the player inventory was updated correctly. - let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50); - assert_eq!(player_inventory.slot(expected_player_slot_id), None); - } - - #[test] - fn dragging_items() { - let mut app = App::new(); - let (client_ent, mut client_helper) = scenario_single_client(&mut app); - app.world.get_mut::(client_ent).unwrap().0 = - Some(ItemStack::new(ItemKind::Diamond, 64, None)); - - // Process a tick to get past the "on join" logic. - app.update(); - client_helper.clear_sent(); - - let inv_state = app.world.get::(client_ent).unwrap(); - let window_id = inv_state.window_id; - let state_id = inv_state.state_id.0; - - let drag_packet = ClickSlotC2s { - window_id, - state_id: VarInt(state_id), - slot_idx: -999, - button: 2, - mode: ClickMode::Drag, - slot_changes: vec![ - Slot { - idx: 9, - item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), - }, - Slot { - idx: 10, - item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), - }, - Slot { - idx: 11, - item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), - }, - ], - carried_item: Some(ItemStack::new(ItemKind::Diamond, 1, None)), - }; - client_helper.send(&drag_packet); - - app.update(); - let sent_packets = client_helper.collect_sent(); - assert_eq!(sent_packets.len(), 0); - - let cursor_item = app - .world - .get::(client_ent) - .expect("could not find client"); - assert_eq!( - cursor_item.0, - Some(ItemStack::new(ItemKind::Diamond, 1, None)) - ); - let inventory = app - .world - .get::(client_ent) - .expect("could not find inventory"); - for i in 9..12 { - assert_eq!( - inventory.slot(i), - Some(&ItemStack::new(ItemKind::Diamond, 21, None)) - ); - } - } } diff --git a/crates/valence/src/inventory/validate.rs b/crates/valence_inventory/src/validate.rs similarity index 97% rename from crates/valence/src/inventory/validate.rs rename to crates/valence_inventory/src/validate.rs index fb3b106..d39f073 100644 --- a/crates/valence/src/inventory/validate.rs +++ b/crates/valence_inventory/src/validate.rs @@ -1,9 +1,9 @@ use anyhow::{bail, ensure}; -use valence_protocol::packet::c2s::play::click_slot::ClickMode; -use valence_protocol::packet::c2s::play::ClickSlotC2s; +use valence_core::item::ItemStack; +use valence_core::packet::c2s::play::click_slot::ClickMode; +use valence_core::packet::c2s::play::ClickSlotC2s; -use super::{Inventory, InventoryWindow, PLAYER_INVENTORY_MAIN_SLOTS_COUNT}; -use crate::client::CursorItem; +use super::{CursorItem, Inventory, InventoryWindow, PLAYER_INVENTORY_MAIN_SLOTS_COUNT}; /// Validates a click slot packet enforcing that all fields are valid. pub(super) fn validate_click_slot_packet( @@ -35,7 +35,7 @@ pub(super) fn validate_click_slot_packet( .item .max_stack() .max(slot.count()) - .min(valence_protocol::item::STACK_MAX); + .min(ItemStack::STACK_MAX); if !(1..=max_stack_size).contains(&slot.count()) { return false; } @@ -52,7 +52,7 @@ pub(super) fn validate_click_slot_packet( .item .max_stack() .max(carried_item.count()) - .min(valence_protocol::item::STACK_MAX); + .min(ItemStack::STACK_MAX); ensure!( (1..=max_stack_size).contains(&carried_item.count()), "invalid carried item count" @@ -356,13 +356,13 @@ fn calculate_net_item_delta( } #[cfg(test)] -mod test { - use valence_protocol::item::{ItemKind, ItemStack}; - use valence_protocol::packet::c2s::play::click_slot::Slot; - use valence_protocol::var_int::VarInt; +mod tests { + use valence_core::item::{ItemKind, ItemStack}; + use valence_core::packet::c2s::play::click_slot::Slot; + use valence_core::packet::var_int::VarInt; use super::*; - use crate::prelude::InventoryKind; + use crate::InventoryKind; #[test] fn net_item_delta_1() { diff --git a/crates/valence_nbt/Cargo.toml b/crates/valence_nbt/Cargo.toml index ed5a656..8b95af2 100644 --- a/crates/valence_nbt/Cargo.toml +++ b/crates/valence_nbt/Cargo.toml @@ -2,20 +2,19 @@ name = "valence_nbt" description = "A library for Minecraft's Named Binary Tag (NBT) format." documentation = "https://docs.rs/valence_nbt/" -repository = "https://github.com/valence-rs/valence/tree/main/valence_nbt" +repository = "https://github.com/valence-rs/valence/tree/main/crates/valence_nbt" readme = "README.md" -license = "MIT" +license.workspace = true keywords = ["nbt", "minecraft", "serialization"] version = "0.5.0" -authors = ["Ryan Johnson "] -edition = "2021" - -[dependencies] -byteorder = "1.4.3" -cesu8 = "1.1.0" -indexmap = { version = "1.9.1", optional = true } -uuid = { version = "1.1.2", optional = true } +edition.workspace = true [features] # When enabled, the order of fields in compounds are preserved. preserve_order = ["dep:indexmap"] + +[dependencies] +byteorder.workspace = true +cesu8.workspace = true +indexmap = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } diff --git a/crates/valence_nbt/README.md b/crates/valence_nbt/README.md index cb1efe9..4aeba9e 100644 --- a/crates/valence_nbt/README.md +++ b/crates/valence_nbt/README.md @@ -1,8 +1,52 @@ # valence_nbt -A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT) format. - -See the [documentation] for more information. +A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT) +format. [Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format -[documentation]: https://docs.rs/valence_nbt/ + +# Examples + +Encode NBT data to its binary form. We are using the [`compound!`] macro to +conveniently construct [`Compound`] values. + +```rust +use valence_nbt::{compound, to_binary_writer, List}; + +let c = compound! { + "byte" => 5_i8, + "string" => "hello", + "list_of_float" => List::Float(vec![ + 3.1415, + 2.7182, + 1.4142 + ]), +}; + +let mut buf = vec![]; + +to_binary_writer(&mut buf, &c, "").unwrap(); +``` + +Decode NBT data from its binary form. + +```rust +use valence_nbt::{compound, from_binary_slice}; + +let some_bytes = [10, 0, 0, 3, 0, 3, 105, 110, 116, 0, 0, 222, 173, 0]; + +let expected_value = compound! { + "int" => 0xdead +}; + +let (nbt, root_name) = from_binary_slice(&mut some_bytes.as_slice()).unwrap(); + +assert_eq!(nbt, expected_value); +assert_eq!(root_name, ""); +``` + +# Features + +- `preserve_order`: Causes the order of fields in [`Compound`]s to be +preserved during insertion and deletion at a slight cost to performance. +The iterators on `Compound` can then implement [`DoubleEndedIterator`]. diff --git a/crates/valence_nbt/src/lib.rs b/crates/valence_nbt/src/lib.rs index 28725ae..5038f92 100644 --- a/crates/valence_nbt/src/lib.rs +++ b/crates/valence_nbt/src/lib.rs @@ -1,70 +1,21 @@ -//! A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT) -//! format. -//! -//! [Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format -//! -//! # Examples -//! -//! Encode NBT data to its binary form. We are using the [`compound!`] macro to -//! conveniently construct [`Compound`] values. -//! -//! ```rust -//! use valence_nbt::{compound, to_binary_writer, List}; -//! -//! let c = compound! { -//! "byte" => 5_i8, -//! "string" => "hello", -//! "list_of_float" => List::Float(vec![ -//! 3.1415, -//! 2.7182, -//! 1.4142 -//! ]), -//! }; -//! -//! let mut buf = vec![]; -//! -//! to_binary_writer(&mut buf, &c, "").unwrap(); -//! ``` -//! -//! Decode NBT data from its binary form. -//! -//! ```rust -//! use valence_nbt::{compound, from_binary_slice}; -//! -//! let some_bytes = [10, 0, 0, 3, 0, 3, 105, 110, 116, 0, 0, 222, 173, 0]; -//! -//! let expected_value = compound! { -//! "int" => 0xdead -//! }; -//! -//! let (nbt, root_name) = from_binary_slice(&mut some_bytes.as_slice()).unwrap(); -//! -//! assert_eq!(nbt, expected_value); -//! assert_eq!(root_name, ""); -//! ``` -//! -//! # Features -//! -//! - `preserve_order`: Causes the order of fields in [`Compound`]s to be -//! preserved during insertion and deletion at a slight cost to performance. -//! The iterators on `Compound` can then implement [`DoubleEndedIterator`]. - +#![doc = include_str!("../README.md")] #![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 + rustdoc::bare_urls, + rustdoc::invalid_html_tags )] #![warn( trivial_casts, trivial_numeric_casts, unused_lifetimes, unused_import_braces, + unreachable_pub, clippy::dbg_macro )] -#![allow(clippy::unusual_byte_groupings)] pub use compound::Compound; pub use error::Error; diff --git a/crates/valence_nbt/src/modified_utf8.rs b/crates/valence_nbt/src/modified_utf8.rs index 6316cd7..bfb800b 100644 --- a/crates/valence_nbt/src/modified_utf8.rs +++ b/crates/valence_nbt/src/modified_utf8.rs @@ -10,7 +10,7 @@ use std::str::from_utf8_unchecked; use byteorder::{BigEndian, WriteBytesExt}; -pub fn write_modified_utf8(mut writer: impl Write, text: &str) -> io::Result<()> { +pub(crate) fn write_modified_utf8(mut writer: impl Write, text: &str) -> io::Result<()> { let bytes = text.as_bytes(); let mut i = 0; @@ -76,7 +76,7 @@ fn encode_surrogate(surrogate: u16) -> [u8; 3] { ] } -pub fn encoded_len(text: &str) -> usize { +pub(crate) fn encoded_len(text: &str) -> usize { let mut n = 0; let mut i = 0; let bytes = text.as_bytes(); diff --git a/crates/valence_network/Cargo.toml b/crates/valence_network/Cargo.toml new file mode 100644 index 0000000..7263c99 --- /dev/null +++ b/crates/valence_network/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "valence_network" +version.workspace = true +edition.workspace = true + +[features] +default = ["encryption", "compression"] # TODO: remove this. +encryption = ["valence_core/encryption"] +compression = ["valence_core/compression"] + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +base64.workspace = true +bevy_app.workspace = true +bevy_ecs.workspace = true +bytes.workspace = true +flume.workspace = true +hmac.workspace = true +num-bigint.workspace = true +rand.workspace = true +rsa-der.workspace = true +rsa.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +sha1.workspace = true +sha2.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true +uuid.workspace = true +valence_client.workspace = true +valence_core.workspace = true +valence_entity.workspace = true + +[dependencies.reqwest] +workspace = true +default-features = false +# Avoid OpenSSL dependency on Linux. +features = ["rustls-tls", "json"] diff --git a/crates/valence_network/README.md b/crates/valence_network/README.md new file mode 100644 index 0000000..d8f7ec0 --- /dev/null +++ b/crates/valence_network/README.md @@ -0,0 +1,10 @@ +# valence_network + +The plugin responsible for accepting connections and spawning clients. + +This covers everything in the "handshaking", "status" and "login" stages of the protocol, before the main "play" stage begins. Support for proxies like [Velocity] and [BungeeCord] are implemented here. + +Valence users can choose not to include `valence_network` in their project. This could be useful for testing or using Valence as an integrated server in a client. + +[Velocity]: https://papermc.io/software/velocity +[BungeeCord]: https://github.com/SpigotMC/BungeeCord diff --git a/crates/valence/src/server/byte_channel.rs b/crates/valence_network/src/byte_channel.rs similarity index 85% rename from crates/valence/src/server/byte_channel.rs rename to crates/valence_network/src/byte_channel.rs index ea568c9..6fc0fca 100644 --- a/crates/valence/src/server/byte_channel.rs +++ b/crates/valence_network/src/byte_channel.rs @@ -8,7 +8,7 @@ use bytes::BytesMut; use thiserror::Error; use tokio::sync::Notify; -pub fn byte_channel(limit: usize) -> (ByteSender, ByteReceiver) { +pub(crate) fn byte_channel(limit: usize) -> (ByteSender, ByteReceiver) { let shared = Arc::new(Shared { mtx: Mutex::new(Inner { bytes: BytesMut::new(), @@ -27,11 +27,11 @@ pub fn byte_channel(limit: usize) -> (ByteSender, ByteReceiver) { (sender, receiver) } -pub struct ByteSender { +pub(crate) struct ByteSender { shared: Arc, } -pub struct ByteReceiver { +pub(crate) struct ByteReceiver { shared: Arc, } @@ -47,7 +47,7 @@ struct Inner { } impl ByteSender { - pub fn take_capacity(&mut self, additional: usize) -> BytesMut { + pub(crate) fn take_capacity(&mut self, additional: usize) -> BytesMut { let mut lck = self.shared.mtx.lock().unwrap(); lck.bytes.reserve(additional); @@ -56,7 +56,7 @@ impl ByteSender { lck.bytes.split_off(len) } - pub fn try_send(&mut self, mut bytes: BytesMut) -> Result<(), TrySendError> { + pub(crate) fn try_send(&mut self, mut bytes: BytesMut) -> Result<(), TrySendError> { let mut lck = self.shared.mtx.lock().unwrap(); if lck.disconnected { @@ -84,7 +84,7 @@ impl ByteSender { Ok(()) } - pub async fn send_async(&mut self, mut bytes: BytesMut) -> Result<(), SendError> { + pub(crate) async fn send_async(&mut self, mut bytes: BytesMut) -> Result<(), SendError> { loop { { let mut lck = self.shared.mtx.lock().unwrap(); @@ -115,18 +115,18 @@ impl ByteSender { } } - pub fn is_disconnected(&self) -> bool { + pub(crate) fn is_disconnected(&self) -> bool { self.shared.mtx.lock().unwrap().disconnected } - pub fn limit(&self) -> usize { + pub(crate) fn limit(&self) -> usize { self.shared.limit } } /// Contains any excess bytes not sent. #[derive(Clone, PartialEq, Eq, Debug, Error)] -pub enum TrySendError { +pub(crate) enum TrySendError { #[error("sender disconnected")] Disconnected(BytesMut), #[error("channel full (see `Config::outgoing_capacity`)")] @@ -135,16 +135,16 @@ pub enum TrySendError { #[derive(Clone, PartialEq, Eq, Debug, Error)] #[error("sender disconnected")] -pub struct SendError(pub BytesMut); +pub(crate) struct SendError(pub(crate) BytesMut); impl SendError { - pub fn into_inner(self) -> BytesMut { + pub(crate) fn into_inner(self) -> BytesMut { self.0 } } impl ByteReceiver { - pub fn try_recv(&mut self) -> Result { + pub(crate) fn try_recv(&mut self) -> Result { let mut lck = self.shared.mtx.lock().unwrap(); if !lck.bytes.is_empty() { @@ -159,7 +159,7 @@ impl ByteReceiver { Err(TryRecvError::Empty) } - pub async fn recv_async(&mut self) -> Result { + pub(crate) async fn recv_async(&mut self) -> Result { loop { { let mut lck = self.shared.mtx.lock().unwrap(); @@ -178,17 +178,17 @@ impl ByteReceiver { } } - pub fn is_disconnected(&self) -> bool { + pub(crate) fn is_disconnected(&self) -> bool { self.shared.mtx.lock().unwrap().disconnected } - pub fn limit(&self) -> usize { + pub(crate) fn limit(&self) -> usize { self.shared.limit } } #[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum TryRecvError { +pub(crate) enum TryRecvError { #[error("empty channel")] Empty, #[error("receiver disconnected")] @@ -196,7 +196,7 @@ pub enum TryRecvError { } #[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum RecvError { +pub(crate) enum RecvError { #[error("receiver disconnected")] Disconnected, } diff --git a/crates/valence/src/server/connect.rs b/crates/valence_network/src/connect.rs similarity index 72% rename from crates/valence/src/server/connect.rs rename to crates/valence_network/src/connect.rs index ff6cf2a..0339f21 100644 --- a/crates/valence/src/server/connect.rs +++ b/crates/valence_network/src/connect.rs @@ -2,49 +2,45 @@ use std::io; use std::net::SocketAddr; -use std::sync::Arc; use std::time::Duration; use anyhow::{anyhow, bail, ensure, Context}; use base64::prelude::*; use hmac::digest::Update; use hmac::{Hmac, Mac}; -use num::BigInt; +use num_bigint::BigInt; use reqwest::StatusCode; use rsa::PaddingScheme; use serde::Deserialize; use serde_json::{json, Value}; use sha1::Sha1; use sha2::{Digest, Sha256}; -use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::OwnedSemaphorePermit; -use tracing::{error, info, instrument, trace, warn}; +use tracing::{error, info, trace, warn}; use uuid::Uuid; -use valence_protocol::decoder::PacketDecoder; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::packet::c2s::handshake::handshake::NextState; -use valence_protocol::packet::c2s::handshake::HandshakeC2s; -use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s, LoginQueryResponseC2s}; -use valence_protocol::packet::c2s::status::{QueryPingC2s, QueryRequestC2s}; -use valence_protocol::packet::s2c::login::{ +use valence_client::is_valid_username; +use valence_core::packet::c2s::handshake::handshake::NextState; +use valence_core::packet::c2s::handshake::HandshakeC2s; +use valence_core::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s, LoginQueryResponseC2s}; +use valence_core::packet::c2s::status::{QueryPingC2s, QueryRequestC2s}; +use valence_core::packet::decode::PacketDecoder; +use valence_core::packet::encode::PacketEncoder; +use valence_core::packet::raw::RawBytes; +use valence_core::packet::s2c::login::{ LoginCompressionS2c, LoginDisconnectS2c, LoginHelloS2c, LoginQueryRequestS2c, LoginSuccessS2c, }; -use valence_protocol::packet::s2c::status::{QueryPongS2c, QueryResponseS2c}; -use valence_protocol::raw::RawBytes; -use valence_protocol::text::Text; -use valence_protocol::types::Property; -use valence_protocol::var_int::VarInt; -use valence_protocol::{ident, translation_key, Decode, MINECRAFT_VERSION, PROTOCOL_VERSION}; +use valence_core::packet::s2c::status::{QueryPongS2c, QueryResponseS2c}; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::Decode; +use valence_core::property::Property; +use valence_core::text::Text; +use valence_core::{ident, translation_key, MINECRAFT_VERSION, PROTOCOL_VERSION}; -use crate::config::{AsyncCallbacks, ConnectionMode, ServerListPing}; -use crate::server::connection::InitialConnection; -use crate::server::{NewClientInfo, SharedServer}; -use crate::util::is_valid_username; +use crate::packet_io::PacketIo; +use crate::{CleanupOnDrop, ConnectionMode, NewClientInfo, ServerListPing, SharedNetworkState}; /// Accepts new connections to the server as they occur. -#[instrument(skip_all)] -pub async fn do_accept_loop(shared: SharedServer, callbacks: Arc) { +pub(super) async fn do_accept_loop(shared: SharedNetworkState) { let listener = match TcpListener::bind(shared.0.address).await { Ok(listener) => listener, Err(e) => { @@ -57,13 +53,12 @@ pub async fn do_accept_loop(shared: SharedServer, callbacks: Arc match listener.accept().await { Ok((stream, remote_addr)) => { - tokio::spawn(handle_connection( - shared.clone(), - callbacks.clone(), - stream, - remote_addr, - permit, - )); + let shared = shared.clone(); + + tokio::spawn(async move { + handle_connection(shared, stream, remote_addr).await; + drop(permit); + }); } Err(e) => { error!("failed to accept incoming connection: {e}"); @@ -75,34 +70,23 @@ pub async fn do_accept_loop(shared: SharedServer, callbacks: Arc, - stream: TcpStream, - remote_addr: SocketAddr, - permit: OwnedSemaphorePermit, -) { +async fn handle_connection(shared: SharedNetworkState, stream: TcpStream, remote_addr: SocketAddr) { trace!("handling connection"); if let Err(e) = stream.set_nodelay(true) { error!("failed to set TCP_NODELAY: {e}"); } - let (read, write) = stream.into_split(); - - let conn = InitialConnection::new( - read, - write, + let conn = PacketIo::new( + stream, PacketEncoder::new(), PacketDecoder::new(), Duration::from_secs(5), - permit, ); // TODO: peek stream for 0xFE legacy ping - if let Err(e) = handle_handshake(shared, callbacks, conn, remote_addr).await { + if let Err(e) = handle_handshake(shared, conn, remote_addr).await { // EOF can happen if the client disconnects while joining, which isn't // very erroneous. if let Some(e) = e.downcast_ref::() { @@ -121,12 +105,11 @@ struct HandshakeData { } async fn handle_handshake( - shared: SharedServer, - callbacks: Arc, - mut conn: InitialConnection, + shared: SharedNetworkState, + mut io: PacketIo, remote_addr: SocketAddr, ) -> anyhow::Result<()> { - let handshake = conn.recv_packet::().await?; + let handshake = io.recv_packet::().await?; let handshake = HandshakeData { protocol_version: handshake.protocol_version.0, @@ -135,25 +118,26 @@ async fn handle_handshake( }; ensure!( - matches!(shared.connection_mode(), ConnectionMode::BungeeCord) + matches!(&shared.0.connection_mode, ConnectionMode::BungeeCord) || handshake.server_address.chars().count() <= 255, "handshake server address is too long" ); match handshake.next_state { - NextState::Status => handle_status(shared, callbacks, conn, remote_addr, handshake) + NextState::Status => handle_status(shared, io, remote_addr, handshake) .await .context("error handling status"), NextState::Login => { - match handle_login(&shared, callbacks, &mut conn, remote_addr, handshake) + match handle_login(&shared, &mut io, remote_addr, handshake) .await .context("error handling login")? { - Some(info) => { - let client = conn.into_client_args( + Some((info, cleanup)) => { + let client = io.into_client_args( info, - shared.0.incoming_capacity, - shared.0.outgoing_capacity, + shared.0.incoming_byte_limit, + shared.0.outgoing_byte_limit, + cleanup, ); let _ = shared.0.new_clients_send.send_async(client).await; @@ -167,15 +151,17 @@ async fn handle_handshake( } async fn handle_status( - shared: SharedServer, - callbacks: Arc, - mut conn: InitialConnection, + shared: SharedNetworkState, + mut io: PacketIo, remote_addr: SocketAddr, handshake: HandshakeData, ) -> anyhow::Result<()> { - conn.recv_packet::().await?; + io.recv_packet::().await?; - match callbacks + match shared + .0 + .callbacks + .inner .server_list_ping(&shared, remote_addr, handshake.protocol_version) .await { @@ -205,7 +191,7 @@ async fn handle_status( json["favicon"] = Value::String(buf); } - conn.send_packet(&QueryResponseS2c { + io.send_packet(&QueryResponseS2c { json: &json.to_string(), }) .await?; @@ -213,23 +199,22 @@ async fn handle_status( ServerListPing::Ignore => return Ok(()), } - let QueryPingC2s { payload } = conn.recv_packet().await?; + let QueryPingC2s { payload } = io.recv_packet().await?; - conn.send_packet(&QueryPongS2c { payload }).await?; + io.send_packet(&QueryPongS2c { payload }).await?; Ok(()) } /// Handle the login process and return the new client's data if successful. async fn handle_login( - shared: &SharedServer, - callbacks: Arc, - conn: &mut InitialConnection, + shared: &SharedNetworkState, + conn: &mut PacketIo, remote_addr: SocketAddr, handshake: HandshakeData, -) -> anyhow::Result> { +) -> anyhow::Result> { if handshake.protocol_version != PROTOCOL_VERSION { - // TODO: send translated disconnect msg? + // TODO: send translated disconnect msg. return Ok(None); } @@ -243,9 +228,7 @@ async fn handle_login( let username = username.to_owned(); let info = match shared.connection_mode() { - ConnectionMode::Online { .. } => { - login_online(shared, &callbacks, conn, remote_addr, username).await? - } + ConnectionMode::Online { .. } => login_online(shared, conn, remote_addr, username).await?, ConnectionMode::Offline => login_offline(remote_addr, username)?, ConnectionMode::BungeeCord => login_bungeecord(&handshake.server_address, username)?, ConnectionMode::Velocity { secret } => login_velocity(conn, username, secret).await?, @@ -260,14 +243,17 @@ async fn handle_login( conn.set_compression(Some(threshold)); } - if let Err(reason) = callbacks.login(shared, &info).await { - info!("disconnect at login: \"{reason}\""); - conn.send_packet(&LoginDisconnectS2c { - reason: reason.into(), - }) - .await?; - return Ok(None); - } + let cleanup = match shared.0.callbacks.inner.login(shared, &info).await { + Ok(f) => CleanupOnDrop(Some(f)), + Err(reason) => { + info!("disconnect at login: \"{reason}\""); + conn.send_packet(&LoginDisconnectS2c { + reason: reason.into(), + }) + .await?; + return Ok(None); + } + }; conn.send_packet(&LoginSuccessS2c { uuid: info.uuid, @@ -276,14 +262,13 @@ async fn handle_login( }) .await?; - Ok(Some(info)) + Ok(Some((info, cleanup))) } /// Login procedure for online mode. -pub(super) async fn login_online( - shared: &SharedServer, - callbacks: &Arc, - conn: &mut InitialConnection, +async fn login_online( + shared: &SharedNetworkState, + conn: &mut PacketIo, remote_addr: SocketAddr, username: String, ) -> anyhow::Result { @@ -330,7 +315,10 @@ pub(super) async fn login_online( .chain(&shared.0.public_key_der) .finalize(); - let url = callbacks + let url = shared + .0 + .callbacks + .inner .session_server( shared, username.as_str(), @@ -379,7 +367,7 @@ pub(super) async fn login_online( uuid: profile.id, username, ip: remote_addr.ip(), - properties: profile.properties, + properties: profile.properties.into(), }) } @@ -388,24 +376,18 @@ fn auth_digest(bytes: &[u8]) -> String { } /// Login procedure for offline mode. -pub(super) fn login_offline( - remote_addr: SocketAddr, - username: String, -) -> anyhow::Result { +fn login_offline(remote_addr: SocketAddr, username: String) -> anyhow::Result { Ok(NewClientInfo { // Derive the client's UUID from a hash of their username. uuid: Uuid::from_slice(&Sha256::digest(username.as_str())[..16])?, username, - properties: vec![], + properties: vec![].into(), ip: remote_addr.ip(), }) } /// Login procedure for BungeeCord. -pub(super) fn login_bungeecord( - server_address: &str, - username: String, -) -> anyhow::Result { +fn login_bungeecord(server_address: &str, username: String) -> anyhow::Result { // Get data from server_address field of the handshake let [_, client_ip, uuid, properties]: [&str; 4] = server_address .split('\0') @@ -421,14 +403,14 @@ pub(super) fn login_bungeecord( Ok(NewClientInfo { uuid: uuid.parse()?, username, - properties, + properties: properties.into(), ip: client_ip.parse()?, }) } /// Login procedure for Velocity. -pub(super) async fn login_velocity( - conn: &mut InitialConnection, +async fn login_velocity( + io: &mut PacketIo, username: String, velocity_secret: &str, ) -> anyhow::Result { @@ -438,7 +420,7 @@ pub(super) async fn login_velocity( let message_id: i32 = 0; // TODO: make this random? // Send Player Info Request into the Plugin Channel - conn.send_packet(&LoginQueryRequestS2c { + io.send_packet(&LoginQueryRequestS2c { message_id: VarInt(message_id), channel: ident!("velocity:player_info").into(), data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]), @@ -446,7 +428,7 @@ pub(super) async fn login_velocity( .await?; // Get Response - let plugin_response: LoginQueryResponseC2s = conn.recv_packet().await?; + let plugin_response: LoginQueryResponseC2s = io.recv_packet().await?; ensure!( plugin_response.message_id.0 == message_id, @@ -495,7 +477,7 @@ pub(super) async fn login_velocity( Ok(NewClientInfo { uuid, username, - properties, + properties: properties.into(), ip: remote_addr, }) } diff --git a/crates/valence_network/src/lib.rs b/crates/valence_network/src/lib.rs new file mode 100644 index 0000000..40ab0b1 --- /dev/null +++ b/crates/valence_network/src/lib.rs @@ -0,0 +1,554 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + +mod byte_channel; +mod connect; +mod packet_io; + +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use anyhow::Context; +pub use async_trait::async_trait; +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use connect::do_accept_loop; +use flume::{Receiver, Sender}; +use rand::rngs::OsRng; +use rsa::{PublicKeyParts, RsaPrivateKey}; +use serde::Serialize; +use tokio::runtime::{Handle, Runtime}; +use tokio::sync::Semaphore; +use tracing::error; +use uuid::Uuid; +use valence_client::{ClientBundle, ClientBundleArgs, Properties, SpawnClientsSet}; +use valence_core::text::Text; +use valence_core::Server; + +pub struct NetworkPlugin; + +impl Plugin for NetworkPlugin { + fn build(&self, app: &mut App) { + if let Err(e) = build_plugin(app) { + error!("failed to build network plugin: {e:#}"); + } + } +} + +fn build_plugin(app: &mut App) -> anyhow::Result<()> { + let compression_threshold = app + .world + .get_resource::() + .context("missing server resource")? + .compression_threshold(); + + let settings = app + .world + .get_resource_or_insert_with(NetworkSettings::default); + + let (new_clients_send, new_clients_recv) = flume::bounded(64); + + let rsa_key = RsaPrivateKey::new(&mut OsRng, 1024)?; + + let public_key_der = + rsa_der::public_key_to_der(&rsa_key.n().to_bytes_be(), &rsa_key.e().to_bytes_be()) + .into_boxed_slice(); + + let runtime = if settings.tokio_handle.is_none() { + Some(Runtime::new()?) + } else { + None + }; + + let tokio_handle = match &runtime { + Some(rt) => rt.handle().clone(), + None => settings.tokio_handle.clone().unwrap(), + }; + + let shared = SharedNetworkState(Arc::new(SharedNetworkStateInner { + callbacks: settings.callbacks.clone(), + address: settings.address, + incoming_byte_limit: settings.incoming_byte_limit, + outgoing_byte_limit: settings.outgoing_byte_limit, + connection_sema: Arc::new(Semaphore::new( + settings.max_connections.min(Semaphore::MAX_PERMITS), + )), + player_count: AtomicUsize::new(0), + max_players: settings.max_players, + connection_mode: settings.connection_mode.clone(), + compression_threshold, + tokio_handle, + _tokio_runtime: runtime, + new_clients_send, + new_clients_recv, + rsa_key, + public_key_der, + http_client: reqwest::Client::new(), + })); + + app.insert_resource(shared.clone()); + + // System for starting the accept loop. + let start_accept_loop = move |shared: Res| { + let _guard = shared.0.tokio_handle.enter(); + + // Start accepting new connections. + tokio::spawn(do_accept_loop(shared.clone())); + }; + + // System for spawning new clients. + let spawn_new_clients = move |world: &mut World| { + for _ in 0..shared.0.new_clients_recv.len() { + match shared.0.new_clients_recv.try_recv() { + Ok(args) => world.spawn(ClientBundle::new(args)), + Err(_) => break, + }; + } + }; + + // Start accepting connections in `PostStartup` to allow user startup code to + // run first. + app.add_system( + start_accept_loop + .in_schedule(CoreSchedule::Startup) + .in_base_set(StartupSet::PostStartup), + ); + + // Spawn new clients before the event loop starts. + app.add_system(spawn_new_clients.in_set(SpawnClientsSet)); + + Ok(()) +} + +#[derive(Resource, Clone)] +pub struct SharedNetworkState(Arc); + +impl SharedNetworkState { + pub fn connection_mode(&self) -> &ConnectionMode { + &self.0.connection_mode + } + + pub fn player_count(&self) -> &AtomicUsize { + &self.0.player_count + } + + pub fn max_players(&self) -> usize { + self.0.max_players + } +} +struct SharedNetworkStateInner { + callbacks: ErasedNetworkCallbacks, + address: SocketAddr, + incoming_byte_limit: usize, + outgoing_byte_limit: usize, + /// Limits the number of simultaneous connections to the server before the + /// play state. + connection_sema: Arc, + //// The number of clients in the play state, past the login state. + player_count: AtomicUsize, + max_players: usize, + connection_mode: ConnectionMode, + compression_threshold: Option, + tokio_handle: Handle, + // Holding a runtime handle is not enough to keep tokio working. We need + // to store the runtime here so we don't drop it. + _tokio_runtime: Option, + /// Sender for new clients past the login stage. + new_clients_send: Sender, + /// Receiver for new clients past the login stage. + new_clients_recv: Receiver, + /// The RSA keypair used for encryption with clients. + rsa_key: RsaPrivateKey, + /// The public part of `rsa_key` encoded in DER, which is an ASN.1 format. + /// This is sent to clients during the authentication process. + public_key_der: Box<[u8]>, + /// For session server requests. + http_client: reqwest::Client, +} + +/// Contains information about a new client joining the server. +#[derive(Debug)] +#[non_exhaustive] +pub struct NewClientInfo { + /// The username of the new client. + pub username: String, + /// The UUID of the new client. + pub uuid: Uuid, + /// The remote address of the new client. + pub ip: IpAddr, + /// The client's properties from the game profile. Typically contains a + /// `textures` property with the skin and cape of the player. + pub properties: Properties, +} + +/// Settings for [`NetworkPlugin`]. Note that mutations to these fields have no +/// effect after the plugin is built. +#[derive(Resource, Clone)] +pub struct NetworkSettings { + pub callbacks: ErasedNetworkCallbacks, + /// The [`Handle`] to the tokio runtime the server will use. If `None` is + /// provided, the server will create its own tokio runtime at startup. + /// + /// # Default Value + /// + /// `None` + pub tokio_handle: Option, + /// The maximum number of simultaneous initial connections to the server. + /// + /// This only considers the connections _before_ the play state where the + /// client is spawned into the world.. + /// + /// # Default Value + /// + /// The default value is left unspecified and may change in future versions. + pub max_connections: usize, + /// # Default Value + /// + /// `20` + pub max_players: usize, + /// The socket address the server will be bound to. + /// + /// # Default Value + /// + /// `0.0.0.0:25565`, which will listen on every available network interface. + pub address: SocketAddr, + /// The connection mode. This determines if client authentication and + /// encryption should take place and if the server should get the player + /// data from a proxy. + /// + /// **NOTE:** Mutations to this field have no effect if + /// + /// # Default Value + /// + /// [`ConnectionMode::Online`] + pub connection_mode: ConnectionMode, + /// The maximum capacity (in bytes) of the buffer used to hold incoming + /// packet data. + /// + /// A larger capacity reduces the chance that a client needs to be + /// disconnected due to a full buffer, but increases potential + /// memory usage. + /// + /// # Default Value + /// + /// The default value is left unspecified and may change in future versions. + pub incoming_byte_limit: usize, + /// The maximum capacity (in bytes) of the buffer used to hold outgoing + /// packet data. + /// + /// A larger capacity reduces the chance that a client needs to be + /// disconnected due to a full buffer, but increases potential + /// memory usage. + /// + /// # Default Value + /// + /// The default value is left unspecified and may change in future versions. + pub outgoing_byte_limit: usize, +} + +impl Default for NetworkSettings { + fn default() -> Self { + Self { + callbacks: ErasedNetworkCallbacks::default(), + tokio_handle: None, + max_connections: 1024, + max_players: 20, + address: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 25565).into(), + connection_mode: ConnectionMode::Online { + prevent_proxy_connections: false, + }, + incoming_byte_limit: 2097152, // 2 MiB + outgoing_byte_limit: 8388608, // 8 MiB + } + } +} + +/// A type-erased wrapper around an [`NetworkCallbacks`] object. +#[derive(Clone)] +pub struct ErasedNetworkCallbacks { + // TODO: do some shenanigans when async-in-trait is stabilized. + inner: Arc, +} + +impl ErasedNetworkCallbacks { + pub fn new(callbacks: impl NetworkCallbacks) -> Self { + Self { + inner: Arc::new(callbacks), + } + } +} + +impl Default for ErasedNetworkCallbacks { + fn default() -> Self { + Self { + inner: Arc::new(()), + } + } +} + +impl From for ErasedNetworkCallbacks { + fn from(value: T) -> Self { + Self::new(value) + } +} + +/// This trait uses [`mod@async_trait`]. +#[async_trait] +pub trait NetworkCallbacks: Send + Sync + 'static { + /// Called when the server receives a Server List Ping query. + /// Data for the response can be provided or the query can be ignored. + /// + /// This function is called from within a tokio runtime. + /// + /// # Default Implementation + /// + /// A default placeholder response is returned. + async fn server_list_ping( + &self, + shared: &SharedNetworkState, + remote_addr: SocketAddr, + protocol_version: i32, + ) -> ServerListPing { + #![allow(unused_variables)] + + ServerListPing::Respond { + online_players: shared.player_count().load(Ordering::Relaxed) as i32, + max_players: shared.max_players() as i32, + player_sample: vec![], + description: "A Valence Server".into(), + favicon_png: &[], + } + } + + /// Called for each client (after successful authentication if online mode + /// is enabled) to determine if they can join the server. + /// - If `Err(reason)` is returned, then the client is immediately + /// disconnected with `reason` as the displayed message. + /// - Otherwise, `Ok(f)` is returned and the client will continue the login + /// process. This _may_ result in a new client being spawned with the + /// [`ClientBundle`] components. `f` is stored along with the client and + /// is called when the client is disconnected. + /// + /// `f` is a callback function used for handling resource cleanup when the + /// client is dropped. This is useful because a new client entity is not + /// necessarily spawned into the world after a successful login. + /// + /// This method is called from within a tokio runtime, and is the + /// appropriate place to perform asynchronous operations such as + /// database queries which may take some time to complete. + /// + /// # Default Implementation + /// + /// TODO + /// + /// [`Client`]: valence::client::Client + async fn login( + &self, + shared: &SharedNetworkState, + info: &NewClientInfo, + ) -> Result { + let _ = info; + + let max_players = shared.max_players(); + + let success = shared + .player_count() + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| { + if n < max_players { + Some(n + 1) + } else { + None + } + }) + .is_ok(); + + if success { + let shared = shared.clone(); + + Ok(Box::new(move || { + let prev = shared.player_count().fetch_sub(1, Ordering::SeqCst); + debug_assert_ne!(prev, 0, "player count underflowed"); + })) + } else { + // TODO: use correct translation key. + Err("Server Full".into()) + } + } + + /// Called upon every client login to obtain the full URL to use for session + /// server requests. This is done to authenticate player accounts. This + /// method is not called unless [online mode] is enabled. + /// + /// It is assumed that upon successful request, a structure matching the + /// description in the [wiki](https://wiki.vg/Protocol_Encryption#Server) was obtained. + /// Providing a URL that does not return such a structure will result in a + /// disconnect for every client that connects. + /// + /// The arguments are described in the linked wiki article. + /// + /// # Default Implementation + /// + /// Uses the official Minecraft session server. This is formatted as + /// `https://sessionserver.mojang.com/session/minecraft/hasJoined?username=&serverId=&ip=`. + /// + /// [online mode]: ConnectionMode::Online + async fn session_server( + &self, + shared: &SharedNetworkState, + username: &str, + auth_digest: &str, + player_ip: &IpAddr, + ) -> String { + if shared.connection_mode() + == (&ConnectionMode::Online { + prevent_proxy_connections: true, + }) + { + format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={auth_digest}&ip={player_ip}") + } else { + format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={auth_digest}") + } + } +} + +/// A callback function called when the associated client is dropped. See +/// [`NetworkCallbacks::login`] for more information. +pub type CleanupFn = Box; +struct CleanupOnDrop(Option); + +impl Drop for CleanupOnDrop { + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f(); + } + } +} + +/// The default network callbacks. Useful as a placeholder. +impl NetworkCallbacks for () {} + +/// Describes how new connections to the server are handled. +#[derive(Clone, PartialEq)] +#[non_exhaustive] +pub enum ConnectionMode { + /// The "online mode" fetches all player data (username, UUID, and + /// properties) from the [configured session server] and enables + /// encryption. + /// + /// This mode should be used by all publicly exposed servers which are not + /// behind a proxy. + /// + /// [configured session server]: NetworkCallbacks::session_server + Online { + /// Determines if client IP validation should take place during + /// authentication. + /// + /// When `prevent_proxy_connections` is enabled, clients can no longer + /// log-in if they connected to the Yggdrasil server using a different + /// IP than the one used to connect to this server. + /// + /// This is used by the default implementation of + /// [`NetworkCallbacks::session_server`]. A different implementation may + /// choose to ignore this value. + prevent_proxy_connections: bool, + }, + /// Disables client authentication with the configured session server. + /// Clients can join with any username and UUID they choose, potentially + /// gaining privileges they would not otherwise have. Additionally, + /// encryption is disabled and Minecraft's default skins will be used. + /// + /// This mode should be used for development purposes only and not for + /// publicly exposed servers. + Offline, + /// This mode should be used under one of the following situations: + /// - The server is behind a [BungeeCord]/[Waterfall] proxy with IP + /// forwarding enabled. + /// - The server is behind a [Velocity] proxy configured to use the `legacy` + /// forwarding mode. + /// + /// All player data (username, UUID, and properties) is fetched from the + /// proxy, but no attempt is made to stop connections originating from + /// elsewhere. As a result, you must ensure clients connect through the + /// proxy and are unable to connect to the server directly. Otherwise, + /// clients can use any username or UUID they choose similar to + /// [`ConnectionMode::Offline`]. + /// + /// To protect against this, a firewall can be used. However, + /// [`ConnectionMode::Velocity`] is recommended as a secure alternative. + /// + /// [BungeeCord]: https://www.spigotmc.org/wiki/bungeecord/ + /// [Waterfall]: https://github.com/PaperMC/Waterfall + /// [Velocity]: https://velocitypowered.com/ + BungeeCord, + /// This mode is used when the server is behind a [Velocity] proxy + /// configured with the forwarding mode `modern`. + /// + /// All player data (username, UUID, and properties) is fetched from the + /// proxy and all connections originating from outside Velocity are + /// blocked. + /// + /// [Velocity]: https://velocitypowered.com/ + Velocity { + /// The secret key used to prevent connections from outside Velocity. + /// The proxy and Valence must be configured to use the same secret key. + secret: Arc, + }, +} + +/// The result of the Server List Ping [callback]. +/// +/// [callback]: NetworkCallbacks::server_list_ping +#[derive(Clone, Default, Debug)] +pub enum ServerListPing<'a> { + /// Responds to the server list ping with the given information. + Respond { + /// Displayed as the number of players on the server. + online_players: i32, + /// Displayed as the maximum number of players allowed on the server at + /// a time. + max_players: i32, + /// The list of players visible by hovering over the player count. + /// + /// Has no effect if this list is empty. + player_sample: Vec, + /// A description of the server. + description: Text, + /// The server's icon as the bytes of a PNG image. + /// The image must be 64x64 pixels. + /// + /// No icon is used if the slice is empty. + favicon_png: &'a [u8], + }, + /// Ignores the query and disconnects from the client. + #[default] + Ignore, +} + +/// Represents an individual entry in the player sample. +#[derive(Clone, Debug, Serialize)] +pub struct PlayerSampleEntry { + /// The name of the player. + /// + /// This string can contain + /// [legacy formatting codes](https://minecraft.fandom.com/wiki/Formatting_codes). + pub name: String, + /// The player UUID. + pub id: Uuid, +} diff --git a/crates/valence/src/server/connection.rs b/crates/valence_network/src/packet_io.rs similarity index 76% rename from crates/valence/src/server/connection.rs rename to crates/valence_network/src/packet_io.rs index a463be2..ef7e5a3 100644 --- a/crates/valence/src/server/connection.rs +++ b/crates/valence_network/src/packet_io.rs @@ -5,67 +5,58 @@ use std::{io, mem}; use anyhow::bail; use bytes::{Buf, BytesMut}; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use tokio::sync::{OwnedSemaphorePermit, Semaphore}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::sync::Semaphore; use tokio::task::JoinHandle; use tokio::time::timeout; use tracing::{debug, warn}; -use valence_protocol::decoder::{decode_packet, PacketDecoder}; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::var_int::VarInt; -use valence_protocol::{Decode, Packet}; +use valence_client::{ClientBundleArgs, ClientConnection, ReceivedPacket}; +use valence_core::packet::decode::{decode_packet, PacketDecoder}; +use valence_core::packet::encode::PacketEncoder; +use valence_core::packet::var_int::VarInt; +use valence_core::packet::{Decode, Packet}; -use crate::client::{ClientConnection, ReceivedPacket}; -use crate::server::byte_channel::{byte_channel, ByteSender, TrySendError}; -use crate::server::NewClientInfo; +use crate::byte_channel::{byte_channel, ByteSender, TrySendError}; +use crate::{CleanupOnDrop, NewClientInfo}; -pub(super) struct InitialConnection { - reader: R, - writer: W, +pub(crate) struct PacketIo { + stream: TcpStream, enc: PacketEncoder, dec: PacketDecoder, frame: BytesMut, timeout: Duration, - permit: OwnedSemaphorePermit, } const READ_BUF_SIZE: usize = 4096; -impl InitialConnection -where - R: AsyncRead + Unpin, - W: AsyncWrite + Unpin, -{ - pub fn new( - reader: R, - writer: W, +impl PacketIo { + pub(crate) fn new( + stream: TcpStream, enc: PacketEncoder, dec: PacketDecoder, timeout: Duration, - permit: OwnedSemaphorePermit, ) -> Self { Self { - reader, - writer, + stream, enc, dec, frame: BytesMut::new(), timeout, - permit, } } - pub async fn send_packet<'a, P>(&mut self, pkt: &P) -> anyhow::Result<()> + pub(crate) async fn send_packet<'a, P>(&mut self, pkt: &P) -> anyhow::Result<()> where P: Packet<'a>, { self.enc.append_packet(pkt)?; let bytes = self.enc.take(); - timeout(self.timeout, self.writer.write_all(&bytes)).await??; + timeout(self.timeout, self.stream.write_all(&bytes)).await??; Ok(()) } - pub async fn recv_packet<'a, P>(&'a mut self) -> anyhow::Result

+ pub(crate) async fn recv_packet<'a, P>(&'a mut self) -> anyhow::Result

where P: Packet<'a>, { @@ -80,7 +71,7 @@ where self.dec.reserve(READ_BUF_SIZE); let mut buf = self.dec.take_capacity(); - if self.reader.read_buf(&mut buf).await? == 0 { + if self.stream.read_buf(&mut buf).await? == 0 { return Err(io::Error::from(ErrorKind::UnexpectedEof).into()); } @@ -93,31 +84,32 @@ where } #[allow(dead_code)] - pub fn set_compression(&mut self, threshold: Option) { + pub(crate) fn set_compression(&mut self, threshold: Option) { self.enc.set_compression(threshold); self.dec.set_compression(threshold); } - pub fn enable_encryption(&mut self, key: &[u8; 16]) { + pub(crate) fn enable_encryption(&mut self, key: &[u8; 16]) { self.enc.enable_encryption(key); self.dec.enable_encryption(key); } - pub fn into_client_args( + pub(crate) fn into_client_args( mut self, info: NewClientInfo, - incoming_limit: usize, - outgoing_limit: usize, - ) -> NewClientArgs - where - R: Send + 'static, - W: Send + 'static, - { + incoming_byte_limit: usize, + outgoing_byte_limit: usize, + cleanup: CleanupOnDrop, + ) -> ClientBundleArgs { let (incoming_sender, incoming_receiver) = flume::unbounded(); - let recv_sem = Arc::new(Semaphore::new(incoming_limit)); + let incoming_byte_limit = incoming_byte_limit.min(Semaphore::MAX_PERMITS); + + let recv_sem = Arc::new(Semaphore::new(incoming_byte_limit)); let recv_sem_clone = recv_sem.clone(); + let (mut reader, mut writer) = self.stream.into_split(); + let reader_task = tokio::spawn(async move { let mut buf = BytesMut::new(); @@ -128,7 +120,7 @@ where // Incomplete packet. Need more data. buf.reserve(READ_BUF_SIZE); - match self.reader.read_buf(&mut buf).await { + match reader.read_buf(&mut buf).await { Ok(0) => break, // Reader is at EOF. Ok(_) => {} Err(e) => { @@ -168,10 +160,10 @@ where // Estimate memory usage of this packet. let cost = mem::size_of::() + data.len(); - if cost > incoming_limit { + if cost > incoming_byte_limit { debug!( cost, - incoming_limit, + incoming_byte_limit, "cost of received packet is greater than the incoming memory limit" ); // We would never acquire enough permits, so we should exit instead of getting @@ -201,7 +193,7 @@ where } }); - let (outgoing_sender, mut outgoing_receiver) = byte_channel(outgoing_limit); + let (outgoing_sender, mut outgoing_receiver) = byte_channel(outgoing_byte_limit); let writer_task = tokio::spawn(async move { loop { @@ -213,53 +205,41 @@ where } }; - if let Err(e) = self.writer.write_all(&bytes).await { + if let Err(e) = writer.write_all(&bytes).await { debug!("error writing data to stream: {e}"); } } }); - NewClientArgs { - info, + ClientBundleArgs { + username: info.username, + uuid: info.uuid, + ip: info.ip, + properties: info.properties.0, conn: Box::new(RealClientConnection { send: outgoing_sender, recv: incoming_receiver, recv_sem: recv_sem_clone, - _client_permit: self.permit, reader_task, writer_task, + _cleanup: cleanup, }), enc: self.enc, } } } -pub struct NewClientArgs { - pub info: NewClientInfo, - pub conn: Box, - pub enc: PacketEncoder, -} - struct RealClientConnection { send: ByteSender, recv: flume::Receiver, /// Limits the amount of data queued in the `recv` channel. Each permit /// represents one byte. recv_sem: Arc, - /// Limits the number of new clients that can connect to the server. Permit - /// is released when the connection is dropped. - _client_permit: OwnedSemaphorePermit, + _cleanup: CleanupOnDrop, reader_task: JoinHandle<()>, writer_task: JoinHandle<()>, } -impl Drop for RealClientConnection { - fn drop(&mut self) { - self.writer_task.abort(); - self.reader_task.abort(); - } -} - impl ClientConnection for RealClientConnection { fn try_send(&mut self, bytes: BytesMut) -> anyhow::Result<()> { match self.send.try_send(bytes) { @@ -291,3 +271,10 @@ impl ClientConnection for RealClientConnection { self.recv.len() } } + +impl Drop for RealClientConnection { + fn drop(&mut self) { + self.writer_task.abort(); + self.reader_task.abort(); + } +} diff --git a/crates/valence_player_list/Cargo.toml b/crates/valence_player_list/Cargo.toml new file mode 100644 index 0000000..d32714a --- /dev/null +++ b/crates/valence_player_list/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "valence_player_list" +version.workspace = true +edition.workspace = true + +[dependencies] +bevy_app.workspace = true +bevy_ecs.workspace = true +valence_core.workspace = true +valence_client.workspace = true +valence_instance.workspace = true +uuid.workspace = true \ No newline at end of file diff --git a/crates/valence_player_list/README.md b/crates/valence_player_list/README.md new file mode 100644 index 0000000..70450e4 --- /dev/null +++ b/crates/valence_player_list/README.md @@ -0,0 +1,6 @@ +# valence_player_list + +Manages Minecraft's "player list" which is the table seen while holding the tab key in game. + +Correctly updating and sending the player list is necessary for player entities to be rendered by clients. + diff --git a/crates/valence/src/player_list.rs b/crates/valence_player_list/src/lib.rs similarity index 81% rename from crates/valence/src/player_list.rs rename to crates/valence_player_list/src/lib.rs index f65de11..1091d39 100644 --- a/crates/valence/src/player_list.rs +++ b/crates/valence_player_list/src/lib.rs @@ -1,36 +1,66 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use std::borrow::Cow; use bevy_app::prelude::*; use bevy_ecs::prelude::*; use uuid::Uuid; -use valence_protocol::packet::s2c::play::player_list::{Actions, Entry, PlayerListS2c}; -use valence_protocol::packet::s2c::play::{PlayerListHeaderS2c, PlayerRemoveS2c}; -use valence_protocol::text::Text; +use valence_client::{Client, Ping, Properties, Username}; +use valence_core::despawn::Despawned; +use valence_core::game_mode::GameMode; +use valence_core::packet::encode::{PacketWriter, WritePacket}; +use valence_core::packet::s2c::play::player_list::{Actions, Entry, PlayerListS2c}; +use valence_core::packet::s2c::play::{PlayerListHeaderS2c, PlayerRemoveS2c}; +use valence_core::text::Text; +use valence_core::uuid::UniqueId; +use valence_core::Server; +use valence_instance::WriteUpdatePacketsToInstancesSet; -use crate::client::Client; -use crate::component::{Despawned, GameMode, Ping, Properties, UniqueId, Username}; -use crate::instance::WriteUpdatePacketsToInstancesSet; -use crate::packet::{PacketWriter, WritePacket}; -use crate::server::Server; +pub struct PlayerListPlugin; -pub(crate) struct PlayerListPlugin; +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] + +struct PlayerListSet; impl Plugin for PlayerListPlugin { fn build(&self, app: &mut App) { - app.insert_resource(PlayerList::new()).add_systems( - ( - update_header_footer, - add_new_clients_to_player_list, - apply_system_buffers, // So new clients get the packets for their own entry. - update_entries, - init_player_list_for_clients, - remove_despawned_entries, - write_player_list_changes, + app.insert_resource(PlayerList::new()) + .add_systems( + ( + update_header_footer, + add_new_clients_to_player_list, + apply_system_buffers, // So new clients get the packets for their own entry. + update_entries, + init_player_list_for_clients, + remove_despawned_entries, + write_player_list_changes, + ) + .chain() + .in_set(PlayerListSet), ) - .chain() - .before(WriteUpdatePacketsToInstancesSet) - .in_base_set(CoreSet::PostUpdate), - ); + .configure_set( + PlayerListSet + .in_base_set(CoreSet::PostUpdate) + // Needs to happen before player entities are initialized. Otherwise, they will appear invisible. + .before(WriteUpdatePacketsToInstancesSet), + ); } } @@ -159,6 +189,7 @@ fn add_new_clients_to_player_list( } } +#[allow(clippy::type_complexity)] fn init_player_list_for_clients( mut clients: Query<&mut Client, (Added, Without)>, player_list: Res, @@ -194,7 +225,7 @@ fn init_player_list_for_clients( chat_data: None, listed: listed.0, ping: ping.0, - game_mode: (*game_mode).into(), + game_mode: *game_mode, display_name: display_name.0.as_ref().map(Cow::Borrowed), }, ) @@ -246,6 +277,7 @@ fn remove_despawned_entries( } } +#[allow(clippy::type_complexity)] fn update_entries( entries: Query< ( @@ -331,7 +363,7 @@ fn update_entries( chat_data: None, listed: listed.0, ping: ping.0, - game_mode: (*game_mode).into(), + game_mode: *game_mode, display_name: display_name.0.as_ref().map(|x| x.into()), }; diff --git a/crates/valence_protocol/Cargo.toml b/crates/valence_protocol/Cargo.toml deleted file mode 100644 index 96623ea..0000000 --- a/crates/valence_protocol/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[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" -bitfield-struct = "0.3.1" -byteorder = "1.4.3" -bytes = "1.2.1" -cfb8 = { version = "0.7.1", optional = true } -flate2 = { version = "1.0.24", optional = true } -glam = { version = "0.23.0", optional = true } -serde = { version = "1.0.147", features = ["derive"] } -serde_json = "1.0.87" -thiserror = "1.0.37" -tracing = "0.1.37" -uuid = { version = "1.2.1", features = ["serde"] } -valence_protocol_macros = { version = "0.1.0", path = "../valence_protocol_macros" } -valence_nbt = { version = "0.5.0", path = "../valence_nbt" } - -[[bench]] -name = "benches" -harness = false - -[dev-dependencies] -rand = "0.8.5" -criterion = "0.4.0" -valence_protocol = { version = "0.1.0", path = ".", features = ["compression"] } - -[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"] -glam = ["dep:glam"] diff --git a/crates/valence_protocol/build/enchant.rs b/crates/valence_protocol/build/enchant.rs deleted file mode 100644 index c503a7c..0000000 --- a/crates/valence_protocol/build/enchant.rs +++ /dev/null @@ -1,218 +0,0 @@ -use heck::ToPascalCase; -use proc_macro2::TokenStream; -use quote::quote; -use serde::Deserialize; - -use crate::ident; - -#[derive(Deserialize, Debug)] -pub struct Enchantment { - #[allow(unused)] - id: u16, - name: String, - translation_key: String, - min_level: i16, - max_level: i16, - #[serde(alias = "cursed")] - is_curse: bool, - rarity_weight: i32, - #[serde(alias = "sources")] - source: EnchantmentSources, -} - -#[derive(Deserialize, Debug)] -pub struct EnchantmentSources { - treasure: bool, - enchantment_table: bool, - random_selection: bool, -} - -pub fn build() -> anyhow::Result { - let enchants: Vec = - serde_json::from_str(include_str!("../../../extracted/enchants.json"))?; - - let enchantmentkind_definitions = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let id = enchant.id as isize; - quote! { - #rustified_name = #id, - } - }) - .collect::(); - - let enchantmentkind_id_to_variant_lookup = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let id = &enchant.id; - quote! { - #id => Some(Self::#rustified_name), - } - }) - .collect::(); - - let enchantmentkind_names = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let name = &enchant.name; - quote! { - Self::#rustified_name => #name, - } - }) - .collect::(); - - let enchantmentkind_translations = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let translation_key = &enchant.translation_key; - quote! { - Self::#rustified_name => #translation_key, - } - }) - .collect::(); - - let enchantmentkind_min_level = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let min_level = &enchant.min_level; - quote! { - Self::#rustified_name => #min_level, - } - }) - .collect::(); - - let enchantmentkind_max_level = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let max_level = &enchant.max_level; - quote! { - Self::#rustified_name => #max_level, - } - }) - .collect::(); - - let enchantmentkind_is_curse = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let is_curse = &enchant.is_curse; - quote! { - Self::#rustified_name => #is_curse, - } - }) - .collect::(); - - let enchantmentkind_rarity_weight = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let rarity_weight = &enchant.rarity_weight; - quote! { - Self::#rustified_name => #rarity_weight, - } - }) - .collect::(); - - let enchantmentkind_sources = enchants - .iter() - .map(|enchant| { - let rustified_name = ident(enchant.name.to_pascal_case()); - let treasure = &enchant.source.treasure; - let enchantment_table = &enchant.source.enchantment_table; - let random_selection = &enchant.source.random_selection; - quote! { - Self::#rustified_name => EnchantmentSources { - treasure: #treasure, - enchantment_table: #enchantment_table, - random_selection: #random_selection, - }, - } - }) - .collect::(); - - Ok(quote! { - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct EnchantmentSources { - pub treasure: bool, - pub enchantment_table: bool, - pub random_selection: bool, - } - - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum EnchantmentKind { - #enchantmentkind_definitions - } - - impl EnchantmentKind { - /// Constructs an `EnchantmentKind` from a raw enchantment ID. - /// - /// If the given ID is invalid, `None` is returned. - pub const fn from_raw(id: u16) -> Option { - match id{ - #enchantmentkind_id_to_variant_lookup - _ => None - } - } - - /// Returns the raw enchantment ID. - pub const fn to_raw(self) -> u16 { - self as u16 - } - - /// Returns the translation key. - pub const fn translation_key(self) -> &'static str { - match self{ - #enchantmentkind_translations - } - } - - /// Returns the enchantment name the game uses. - pub const fn name(self) -> &'static str { - match self{ - #enchantmentkind_names - } - } - - /// Returns the minimum enchantment level officially supported by Minecraft. - pub const fn min_level(self) -> i16 { - match self{ - #enchantmentkind_min_level - } - } - - /// Returns the maximum enchantment level officially supported by Minecraft. - pub const fn max_level(self) -> i16 { - match self{ - #enchantmentkind_max_level - } - } - - /// Returns true if the enchantment is of the curse type. - pub const fn is_curse(self) -> bool { - match self{ - #enchantmentkind_is_curse - } - } - - /// Returns the rarity of the enchant. Lower means more rare. - pub const fn rarity_weight(self) -> i32 { - match self{ - #enchantmentkind_rarity_weight - } - } - - /// Returns the different sources this enchantment has. - pub const fn sources(self) -> EnchantmentSources { - match self{ - #enchantmentkind_sources - } - } - } - }) -} diff --git a/crates/valence_protocol/build/main.rs b/crates/valence_protocol/build/main.rs deleted file mode 100644 index 0cb7303..0000000 --- a/crates/valence_protocol/build/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -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; -mod packet_id; -mod sound; -mod translation_key; - -pub fn main() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed=../../extracted/"); - - let generators = [ - (block::build as fn() -> _, "block.rs"), - (enchant::build, "enchant.rs"), - (item::build, "item.rs"), - (sound::build, "sound.rs"), - (translation_key::build, "translation_key.rs"), - (packet_id::build, "packet_id.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) -> 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()), - } -} diff --git a/crates/valence_protocol/src/enchant.rs b/crates/valence_protocol/src/enchant.rs deleted file mode 100644 index 77f993a..0000000 --- a/crates/valence_protocol/src/enchant.rs +++ /dev/null @@ -1,5 +0,0 @@ -// enchant.rs exposes constant values provided by the build script. -// All enchantment variants are located in `EnchantmentKind`. You can use the -// associated const fn functions of `EnchantmentKind` to access details about an -// enchantment type. enchantment specific functions -include!(concat!(env!("OUT_DIR"), "/enchant.rs")); diff --git a/crates/valence_protocol/src/packet.rs b/crates/valence_protocol/src/packet.rs deleted file mode 100644 index 08c992a..0000000 --- a/crates/valence_protocol/src/packet.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! 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 and implements `Packet` for each. -macro_rules! packet_group { - ( - $(#[$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::Packet<$enum_life> for $packet$(<$life>)? { - const PACKET_ID: i32 = crate::packet::id::$packet; - - fn packet_id(&self) -> i32 { - Self::PACKET_ID - } - - fn packet_name(&self) -> &str { - stringify!($packet) - } - - #[allow(unused_imports)] - fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> { - use ::valence_protocol::__private::{Encode, Context, VarInt}; - - VarInt(Self::PACKET_ID) - .encode(&mut w) - .context("failed to encode packet ID")?; - - self.encode(w) - } - - #[allow(unused_imports)] - fn decode_packet(r: &mut &$enum_life [u8]) -> ::valence_protocol::__private::Result { - use ::valence_protocol::__private::{Decode, Context, VarInt, ensure}; - - let id = VarInt::decode(r).context("failed to decode packet ID")?.0; - ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID); - - Self::decode(r) - } - } - )* - - impl<$enum_life> crate::Packet<$enum_life> for $enum_name<$enum_life> { - fn packet_id(&self) -> i32 { - match self { - $( - Self::$packet(_) => <$packet as crate::Packet>::PACKET_ID, - )* - } - } - - fn packet_name(&self) -> &str { - match self { - $( - Self::$packet(pkt) => pkt.packet_name(), - )* - } - } - - fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> { - use crate::Encode; - use crate::var_int::VarInt; - - match self { - $( - Self::$packet(pkt) => { - VarInt(<$packet as crate::Packet>::PACKET_ID).encode(&mut w)?; - pkt.encode(w)?; - } - )* - } - - Ok(()) - } - - fn decode_packet(r: &mut &$enum_life [u8]) -> crate::Result { - use crate::Decode; - use crate::var_int::VarInt; - - let id = VarInt::decode(r)?.0; - Ok(match id { - $( - <$packet as crate::Packet>::PACKET_ID => - Self::$packet($packet::decode(r)?), - )* - id => anyhow::bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)), - }) - } - } - - impl<$enum_life> std::fmt::Debug for $enum_name<$enum_life> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - $( - Self::$packet(pkt) => pkt.fmt(f), - )* - } - } - } - }; - // 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::Packet<'_> for $packet { - const PACKET_ID: i32 = crate::packet::id::$packet; - - fn packet_id(&self) -> i32 { - Self::PACKET_ID - } - - fn packet_name(&self) -> &str { - stringify!($packet) - } - - #[allow(unused_imports)] - fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> { - use ::valence_protocol::__private::{Encode, Context, VarInt}; - - VarInt(Self::PACKET_ID) - .encode(&mut w) - .context("failed to encode packet ID")?; - - self.encode(w) - } - - #[allow(unused_imports)] - fn decode_packet(r: &mut &[u8]) -> ::valence_protocol::__private::Result { - use ::valence_protocol::__private::{Decode, Context, VarInt, ensure}; - - let id = VarInt::decode(r).context("failed to decode packet ID")?.0; - ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID); - - Self::decode(r) - } - } - )* - - impl crate::Packet<'_> for $enum_name { - fn packet_id(&self) -> i32 { - match self { - $( - Self::$packet(_) => <$packet as crate::Packet>::PACKET_ID, - )* - } - } - - fn packet_name(&self) -> &str { - match self { - $( - Self::$packet(pkt) => pkt.packet_name(), - )* - } - } - - fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> { - use crate::Encode; - use crate::var_int::VarInt; - - match self { - $( - Self::$packet(pkt) => { - VarInt(<$packet as crate::Packet>::PACKET_ID).encode(&mut w)?; - pkt.encode(w)?; - } - )* - } - - Ok(()) - } - - fn decode_packet(r: &mut &[u8]) -> crate::Result { - use crate::Decode; - use crate::var_int::VarInt; - - let id = VarInt::decode(r)?.0; - Ok(match id { - $( - <$packet as crate::Packet>::PACKET_ID => - Self::$packet($packet::decode(r)?), - )* - id => anyhow::bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)), - }) - } - } - - impl std::fmt::Debug for $enum_name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - $( - Self::$packet(pkt) => pkt.fmt(f), - )* - } - } - } - } -} - -pub mod c2s; -pub mod s2c; - -/// Contains the packet ID for every packet. The compiler will yell at us when -/// we forget to use one, which is nice. -mod id { - include!(concat!(env!("OUT_DIR"), "/packet_id.rs")); -} diff --git a/crates/valence_protocol/src/packet/s2c/play/open_screen.rs b/crates/valence_protocol/src/packet/s2c/play/open_screen.rs deleted file mode 100644 index e89ce9a..0000000 --- a/crates/valence_protocol/src/packet/s2c/play/open_screen.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::borrow::Cow; - -use crate::text::Text; -use crate::types::WindowType; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; - -#[derive(Clone, Debug, Encode, Decode)] -pub struct OpenScreenS2c<'a> { - pub window_id: VarInt, - pub window_type: WindowType, - pub window_title: Cow<'a, Text>, -} diff --git a/crates/valence_protocol/src/packet/s2c/play/unload_chunk.rs b/crates/valence_protocol/src/packet/s2c/play/unload_chunk.rs deleted file mode 100644 index 06a4ece..0000000 --- a/crates/valence_protocol/src/packet/s2c/play/unload_chunk.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::{Decode, Encode}; - -#[derive(Copy, Clone, Debug, Encode, Decode)] -pub struct UnloadChunkS2c { - pub chunk_x: i32, - pub chunk_z: i32, -} diff --git a/crates/valence_protocol/src/types.rs b/crates/valence_protocol/src/types.rs deleted file mode 100644 index d0de29c..0000000 --- a/crates/valence_protocol/src/types.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Miscellaneous type definitions used in packets. - -use std::borrow::Cow; -use std::io::Write; - -use serde::{Deserialize, Serialize}; - -use crate::block_pos::BlockPos; -use crate::ident::Ident; -use crate::var_int::VarInt; -use crate::{Decode, Encode}; - -#[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, PartialEq, Eq, Default, Debug, Encode, Decode)] -pub enum Hand { - #[default] - Main, - Off, -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)] -pub struct Property { - pub name: S, - pub value: S, - pub signature: Option, -} - -#[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, Default, Encode, Decode)] -pub enum GameMode { - #[default] - Survival, - Creative, - Adventure, - Spectator, -} - -#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] -pub struct GlobalPos<'a> { - pub dimension_name: Ident>, - pub position: BlockPos, -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] -pub enum WindowType { - Generic9x1, - Generic9x2, - Generic9x3, - Generic9x4, - Generic9x5, - Generic9x6, - Generic3x3, - Anvil, - Beacon, - BlastFurnace, - BrewingStand, - Crafting, - Enchantment, - Furnace, - Grindstone, - Hopper, - Lectern, - Loom, - Merchant, - ShulkerBox, - Smithing, - Smoker, - Cartography, - Stonecutter, -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] -pub enum Direction { - /// -Y - Down, - /// +Y - Up, - /// -Z - North, - /// +Z - South, - /// -X - West, - /// +X - East, -} - -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct MessageSignature<'a> { - pub message_id: i32, - pub signature: Option<&'a [u8; 256]>, -} - -impl<'a> Encode for MessageSignature<'a> { - fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { - VarInt(self.message_id + 1).encode(&mut w)?; - - match self.signature { - None => {} - Some(signature) => signature.encode(&mut w)?, - } - - Ok(()) - } -} - -impl<'a> Decode<'a> for MessageSignature<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let message_id = VarInt::decode(r)?.0 - 1; // TODO: this can underflow. - - let signature = if message_id == -1 { - Some(<&[u8; 256]>::decode(r)?) - } else { - None - }; - - Ok(Self { - message_id, - signature, - }) - } -} diff --git a/crates/valence_protocol_macros/Cargo.toml b/crates/valence_protocol_macros/Cargo.toml deleted file mode 100644 index bc3ff98..0000000 --- a/crates/valence_protocol_macros/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "valence_protocol_macros" -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" diff --git a/crates/valence_registry/Cargo.toml b/crates/valence_registry/Cargo.toml new file mode 100644 index 0000000..6160285 --- /dev/null +++ b/crates/valence_registry/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "valence_registry" +version.workspace = true +edition.workspace = true + +[dependencies] +tracing.workspace = true +valence_core.workspace = true +valence_nbt.workspace = true +bevy_ecs.workspace = true +bevy_app.workspace = true \ No newline at end of file diff --git a/crates/valence_registry/README.md b/crates/valence_registry/README.md new file mode 100644 index 0000000..9a598ac --- /dev/null +++ b/crates/valence_registry/README.md @@ -0,0 +1,7 @@ +# valence_registry + +Manages Minecraft's networked registries in a generic way. This includes the registry codec sent to clients during the initial join. + +Consumers of `registry` such as `biome` and `dimension` are expected to update themselves in the registries defined here. Minecraft's default registry codec is loaded by default. + +End users are not expected to use this module directly. diff --git a/crates/valence/src/registry_codec.rs b/crates/valence_registry/src/lib.rs similarity index 88% rename from crates/valence/src/registry_codec.rs rename to crates/valence_registry/src/lib.rs index 0a0e1fe..9d376a1 100644 --- a/crates/valence/src/registry_codec.rs +++ b/crates/valence_registry/src/lib.rs @@ -1,10 +1,71 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use std::collections::BTreeMap; -use bevy_app::{CoreSet, Plugin}; +use bevy_app::prelude::*; pub use bevy_ecs::prelude::*; use tracing::error; +use valence_core::ident::Ident; use valence_nbt::{compound, Compound, List, Value}; -use valence_protocol::ident::Ident; + +pub struct RegistryPlugin; + +/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that +/// modify the registry codec should run _before_ this. +#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct RegistryCodecSet; + +impl Plugin for RegistryPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.init_resource::() + .configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate)) + .add_system(cache_registry_codec.in_set(RegistryCodecSet)); + } +} + +fn cache_registry_codec(codec: ResMut) { + if codec.is_changed() { + let codec = codec.into_inner(); + + codec.cached_codec.clear(); + + for (reg_name, reg) in &codec.registries { + let mut value = vec![]; + + for (id, v) in reg.iter().enumerate() { + value.push(compound! { + "id" => id as i32, + "name" => v.name.as_str(), + "element" => v.element.clone(), + }); + } + + let registry = compound! { + "type" => reg_name.as_str(), + "value" => List::Compound(value), + }; + + codec.cached_codec.insert(reg_name.as_str(), registry); + } + } +} /// Contains the registry codec sent to all players while joining. This contains /// information for biomes and dimensions among other things. @@ -101,45 +162,3 @@ impl Default for RegistryCodec { } } } - -/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that -/// modify the registry codec should run _before_ this. -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct RegistryCodecSet; - -pub(crate) struct RegistryCodecPlugin; - -impl Plugin for RegistryCodecPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.init_resource::() - .configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate)) - .add_system(cache_registry_codec.in_set(RegistryCodecSet)); - } -} - -fn cache_registry_codec(codec: ResMut) { - if codec.is_changed() { - let codec = codec.into_inner(); - - codec.cached_codec.clear(); - - for (reg_name, reg) in &codec.registries { - let mut value = vec![]; - - for (id, v) in reg.iter().enumerate() { - value.push(compound! { - "id" => id as i32, - "name" => v.name.as_str(), - "element" => v.element.clone(), - }); - } - - let registry = compound! { - "type" => reg_name.as_str(), - "value" => List::Compound(value), - }; - - codec.cached_codec.insert(reg_name.as_str(), registry); - } - } -} diff --git a/crates/valence_spatial_index/Cargo.toml b/crates/valence_spatial_index/Cargo.toml index 41632e9..412e9f6 100644 --- a/crates/valence_spatial_index/Cargo.toml +++ b/crates/valence_spatial_index/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "valence_spatial_index" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true [dependencies] -approx = "0.5.1" -rayon = "1.6.0" -vek = "0.15.8" \ No newline at end of file +approx.workspace = true +rayon.workspace = true +vek = "0.15.8" # TODO: remove this. \ No newline at end of file diff --git a/crates/valence_spatial_index/README.md b/crates/valence_spatial_index/README.md new file mode 100644 index 0000000..7c31324 --- /dev/null +++ b/crates/valence_spatial_index/README.md @@ -0,0 +1,3 @@ +# valence_spatial_index + +An implementation of a [bounding volume hierarchy](https://en.wikipedia.org/wiki/Bounding_volume_hierarchy) (BVH) for fast spatial queries. diff --git a/crates/valence_spatial_index/src/lib.rs b/crates/valence_spatial_index/src/lib.rs index 2f9c2d8..df9d7a5 100644 --- a/crates/valence_spatial_index/src/lib.rs +++ b/crates/valence_spatial_index/src/lib.rs @@ -1,3 +1,22 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + use vek::{Aabb, Vec3}; pub mod bvh; diff --git a/crates/valence_stresser/Cargo.toml b/crates/valence_stresser/Cargo.toml deleted file mode 100644 index 6af619a..0000000 --- a/crates/valence_stresser/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "valence_stresser" -description = "A stresser for Valence Minecraft server framework development purposes." -authors = ["qualterz "] -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1.0.69" -clap = { version = "4.1.4", features = ["derive"] } -tokio = { version = "1.25.0", features = ["full"] } -uuid = { version = "1.3.0", features = ["v4"] } -valence_protocol = { version = "0.1.0", path = "../valence_protocol", features = [ - "compression", -] } diff --git a/tools/dump_schedule/Cargo.toml b/tools/dump_schedule/Cargo.toml new file mode 100644 index 0000000..58f6941 --- /dev/null +++ b/tools/dump_schedule/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "dump_schedule" +version.workspace = true +edition.workspace = true + +[dependencies] +bevy_mod_debugdump.workspace = true +valence.path = "../../crates/valence" diff --git a/crates/dump_schedule/README.md b/tools/dump_schedule/README.md similarity index 100% rename from crates/dump_schedule/README.md rename to tools/dump_schedule/README.md diff --git a/crates/dump_schedule/src/main.rs b/tools/dump_schedule/src/main.rs similarity index 58% rename from crates/dump_schedule/src/main.rs rename to tools/dump_schedule/src/main.rs index 013a242..6e193dd 100644 --- a/crates/dump_schedule/src/main.rs +++ b/tools/dump_schedule/src/main.rs @@ -1,14 +1,31 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + clippy::dbg_macro +)] + use std::io; use std::io::Write; use std::process::{Command, Stdio}; -use valence::bevy_app::prelude::*; -use valence::config::ServerPlugin; +use valence::prelude::*; fn main() -> io::Result<()> { let mut app = App::new(); - app.add_plugin(ServerPlugin::new(())); + app.add_plugins(DefaultPlugins); let dot_graph = bevy_mod_debugdump::schedule_graph_dot( &mut app, diff --git a/tools/packet_inspector/Cargo.toml b/tools/packet_inspector/Cargo.toml new file mode 100644 index 0000000..79f81b6 --- /dev/null +++ b/tools/packet_inspector/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "packet_inspector" +version.workspace = true +edition.workspace = true +description = "A simple Minecraft proxy for inspecting packets." + +[features] +default = ["syntax_highlighting"] +syntax_highlighting = ["syntect"] + +[dependencies] +anyhow.workspace = true +atty.workspace = true +bytes.workspace = true +clap.workspace = true +directories.workspace = true +eframe = { workspace = true, default-features = false, features = [ + "default_fonts", # Embed the default egui fonts. + "glow", # Use the glow rendering backend. Alternative: "wgpu". +] } +egui.workspace = true +enum-map.workspace = true +owo-colors.workspace = true +regex.workspace = true +rfd.workspace = true +serde = { workspace = true, features = ["derive"] } +syntect = { workspace = true, optional = true, default-features = false, features = [ + "default-fancy", +] } +time = { workspace = true, features = ["local-offset"] } +tokio.workspace = true +toml.workspace = true +tracing-subscriber.workspace = true +valence_nbt = { workspace = true, features = ["preserve_order"] } +valence_core = { workspace = true, features = ["compression"] } diff --git a/crates/packet_inspector/README.md b/tools/packet_inspector/README.md similarity index 100% rename from crates/packet_inspector/README.md rename to tools/packet_inspector/README.md diff --git a/crates/packet_inspector/src/config.rs b/tools/packet_inspector/src/config.rs similarity index 100% rename from crates/packet_inspector/src/config.rs rename to tools/packet_inspector/src/config.rs diff --git a/crates/packet_inspector/src/context.rs b/tools/packet_inspector/src/context.rs similarity index 94% rename from crates/packet_inspector/src/context.rs rename to tools/packet_inspector/src/context.rs index f1dc7e4..633f426 100644 --- a/crates/packet_inspector/src/context.rs +++ b/tools/packet_inspector/src/context.rs @@ -7,13 +7,14 @@ use owo_colors::{OwoColorize, Style}; use regex::Regex; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; -use valence_protocol::decoder::PacketDecoder; -use valence_protocol::packet::c2s::handshake::HandshakeC2s; -use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s}; -use valence_protocol::packet::c2s::status::{QueryPingC2s, QueryRequestC2s}; -use valence_protocol::packet::s2c::login::LoginSuccessS2c; -use valence_protocol::packet::s2c::status::{QueryPongS2c, QueryResponseS2c}; -use valence_protocol::packet::{C2sPlayPacket, S2cLoginPacket, S2cPlayPacket}; +use valence_core::packet::c2s::handshake::HandshakeC2s; +use valence_core::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s}; +use valence_core::packet::c2s::play::C2sPlayPacket; +use valence_core::packet::c2s::status::{QueryPingC2s, QueryRequestC2s}; +use valence_core::packet::decode::PacketDecoder; +use valence_core::packet::s2c::login::{LoginSuccessS2c, S2cLoginPacket}; +use valence_core::packet::s2c::play::S2cPlayPacket; +use valence_core::packet::s2c::status::{QueryPongS2c, QueryResponseS2c}; use crate::packet_widget::{systemtime_strftime, PacketDirection}; use crate::MetaPacket; @@ -114,9 +115,9 @@ impl Packet { ($packet:ident) => { match dec.try_next_packet() { Ok(Some(frame)) => { - if let Ok(pkt) = - <$packet as valence_protocol::Packet>::decode_packet(&mut &frame[..]) - { + if let Ok(pkt) = <$packet as valence_core::packet::Packet>::decode_packet( + &mut &frame[..], + ) { if formatted { format!("{pkt:#?}") } else { diff --git a/crates/packet_inspector/src/hex_viewer.rs b/tools/packet_inspector/src/hex_viewer.rs similarity index 100% rename from crates/packet_inspector/src/hex_viewer.rs rename to tools/packet_inspector/src/hex_viewer.rs diff --git a/crates/packet_inspector/src/main.rs b/tools/packet_inspector/src/main.rs similarity index 96% rename from crates/packet_inspector/src/main.rs rename to tools/packet_inspector/src/main.rs index 14142ae..e3b7a45 100644 --- a/crates/packet_inspector/src/main.rs +++ b/tools/packet_inspector/src/main.rs @@ -1,3 +1,21 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + clippy::dbg_macro +)] + mod config; mod context; mod hex_viewer; @@ -12,6 +30,7 @@ use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; use anyhow::bail; +use bytes::BytesMut; use clap::Parser; use config::ApplicationConfig; use context::{Context, Packet}; @@ -26,16 +45,16 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::Semaphore; use tokio::task::JoinHandle; use tracing_subscriber::filter::LevelFilter; -use valence_protocol::bytes::BytesMut; -use valence_protocol::decoder::PacketDecoder; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::packet::c2s::handshake::handshake::NextState; -use valence_protocol::packet::c2s::handshake::HandshakeC2s; -use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s}; -use valence_protocol::packet::c2s::status::{QueryPingC2s, QueryRequestC2s}; -use valence_protocol::packet::s2c::login::LoginSuccessS2c; -use valence_protocol::packet::s2c::status::{QueryPongS2c, QueryResponseS2c}; -use valence_protocol::packet::{C2sPlayPacket, S2cLoginPacket, S2cPlayPacket}; +use valence_core::packet::c2s::handshake::handshake::NextState; +use valence_core::packet::c2s::handshake::HandshakeC2s; +use valence_core::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s}; +use valence_core::packet::c2s::play::C2sPlayPacket; +use valence_core::packet::c2s::status::{QueryPingC2s, QueryRequestC2s}; +use valence_core::packet::decode::PacketDecoder; +use valence_core::packet::encode::PacketEncoder; +use valence_core::packet::s2c::login::{LoginSuccessS2c, S2cLoginPacket}; +use valence_core::packet::s2c::play::S2cPlayPacket; +use valence_core::packet::s2c::status::{QueryPongS2c, QueryResponseS2c}; use crate::context::{ContextMode, Stage}; use crate::packet_widget::PacketDirection; diff --git a/crates/packet_inspector/src/packet_widget.rs b/tools/packet_inspector/src/packet_widget.rs similarity index 100% rename from crates/packet_inspector/src/packet_widget.rs rename to tools/packet_inspector/src/packet_widget.rs diff --git a/crates/packet_inspector/src/state.rs b/tools/packet_inspector/src/state.rs similarity index 91% rename from crates/packet_inspector/src/state.rs rename to tools/packet_inspector/src/state.rs index 26c7f8c..105cb2b 100644 --- a/crates/packet_inspector/src/state.rs +++ b/tools/packet_inspector/src/state.rs @@ -1,13 +1,13 @@ use std::io::ErrorKind; use std::sync::Arc; +use bytes::BytesMut; use time::OffsetDateTime; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; -use valence_protocol::bytes::BytesMut; -use valence_protocol::decoder::{decode_packet, PacketDecoder}; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::Packet as ValencePacket; +use valence_core::packet::decode::{decode_packet, PacketDecoder}; +use valence_core::packet::encode::PacketEncoder; +use valence_core::packet::Packet as ValencePacket; use crate::context::{Context, Packet, Stage}; use crate::packet_widget::PacketDirection; diff --git a/crates/packet_inspector/src/syntax_highlighting.rs b/tools/packet_inspector/src/syntax_highlighting.rs similarity index 100% rename from crates/packet_inspector/src/syntax_highlighting.rs rename to tools/packet_inspector/src/syntax_highlighting.rs diff --git a/crates/playground/Cargo.toml b/tools/playground/Cargo.toml similarity index 51% rename from crates/playground/Cargo.toml rename to tools/playground/Cargo.toml index 314c8cd..42ec02d 100644 --- a/crates/playground/Cargo.toml +++ b/tools/playground/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "playground" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true [dependencies] anyhow = "1.0.65" glam = "0.23.0" tracing = "0.1.37" tracing-subscriber = "0.3.16" -valence = { path = "../valence" } -valence_protocol = { path = "../valence_protocol" } \ No newline at end of file +valence_core.workspace = true +valence.workspace = true diff --git a/tools/playground/README.md b/tools/playground/README.md new file mode 100644 index 0000000..c6ba5b1 --- /dev/null +++ b/tools/playground/README.md @@ -0,0 +1,3 @@ +# playground + +An environment for testing code or reproducing bugs. Checkout out CONTRIBUTING.md for more information. \ No newline at end of file diff --git a/crates/playground/build.rs b/tools/playground/build.rs similarity index 100% rename from crates/playground/build.rs rename to tools/playground/build.rs diff --git a/crates/playground/src/.gitignore b/tools/playground/src/.gitignore similarity index 100% rename from crates/playground/src/.gitignore rename to tools/playground/src/.gitignore diff --git a/crates/playground/src/extras.rs b/tools/playground/src/extras.rs similarity index 100% rename from crates/playground/src/extras.rs rename to tools/playground/src/extras.rs diff --git a/tools/playground/src/main.rs b/tools/playground/src/main.rs new file mode 100644 index 0000000..b1498cb --- /dev/null +++ b/tools/playground/src/main.rs @@ -0,0 +1,34 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + clippy::dbg_macro +)] + +use tracing::Level; +use valence::app::App; + +#[allow(dead_code)] +mod extras; +mod playground; + +fn main() { + tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .init(); + + let mut app = App::new(); + playground::build_app(&mut app); + app.run(); +} diff --git a/crates/playground/src/playground.template.rs b/tools/playground/src/playground.template.rs similarity index 72% rename from crates/playground/src/playground.template.rs rename to tools/playground/src/playground.template.rs index f74426d..58610c4 100644 --- a/crates/playground/src/playground.template.rs +++ b/tools/playground/src/playground.template.rs @@ -1,4 +1,5 @@ use valence::client::despawn_disconnected_clients; +use valence::network::ConnectionMode; use valence::prelude::*; #[allow(unused_imports)] @@ -7,11 +8,15 @@ use crate::extras::*; const SPAWN_Y: i32 = 64; pub fn build_app(app: &mut App) { - app.add_plugin(ServerPlugin::new(()).with_connection_mode(ConnectionMode::Offline)) - .add_startup_system(setup) - .add_system(init_clients) - .add_system(despawn_disconnected_clients) - .add_system(toggle_gamemode_on_sneak.in_schedule(EventLoopSchedule)); + app.insert_resource(NetworkSettings { + connection_mode: ConnectionMode::Offline, + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(init_clients) + .add_system(despawn_disconnected_clients) + .add_system(toggle_gamemode_on_sneak.in_schedule(EventLoopSchedule)); } fn setup( diff --git a/tools/stresser/Cargo.toml b/tools/stresser/Cargo.toml new file mode 100644 index 0000000..4cb911a --- /dev/null +++ b/tools/stresser/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "stresser" +description = "A stresser for Valence Minecraft server framework development purposes." +authors = ["qualterz "] +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +tokio.workspace = true +uuid = { workspace = true, features = ["v4"] } +valence_core = { workspace = true, features = ["compression"] } diff --git a/tools/stresser/README.md b/tools/stresser/README.md new file mode 100644 index 0000000..7199f39 --- /dev/null +++ b/tools/stresser/README.md @@ -0,0 +1,3 @@ +# stresser + +A Minecraft client for testing server performance under heavy load. (Incomplete) diff --git a/crates/valence_stresser/src/args.rs b/tools/stresser/src/args.rs similarity index 100% rename from crates/valence_stresser/src/args.rs rename to tools/stresser/src/args.rs diff --git a/crates/valence_stresser/src/main.rs b/tools/stresser/src/main.rs similarity index 73% rename from crates/valence_stresser/src/main.rs rename to tools/stresser/src/main.rs index 0d064d3..91cf49c 100644 --- a/crates/valence_stresser/src/main.rs +++ b/tools/stresser/src/main.rs @@ -1,3 +1,21 @@ +#![doc = include_str!("../README.md")] +#![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, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + clippy::dbg_macro +)] + use core::time::Duration; use std::net::ToSocketAddrs; use std::sync::Arc; diff --git a/crates/valence_stresser/src/stresser.rs b/tools/stresser/src/stresser.rs similarity index 87% rename from crates/valence_stresser/src/stresser.rs rename to tools/stresser/src/stresser.rs index 211851d..bc02e3a 100644 --- a/crates/valence_stresser/src/stresser.rs +++ b/tools/stresser/src/stresser.rs @@ -5,15 +5,16 @@ use anyhow::bail; use tokio::io::AsyncWriteExt; use tokio::net::TcpStream; use uuid::Uuid; -use valence_protocol::decoder::{decode_packet, PacketDecoder}; -use valence_protocol::encoder::PacketEncoder; -use valence_protocol::packet::c2s::handshake::handshake::NextState; -use valence_protocol::packet::c2s::handshake::HandshakeC2s; -use valence_protocol::packet::c2s::login::LoginHelloC2s; -use valence_protocol::packet::c2s::play::{KeepAliveC2s, PositionAndOnGround, TeleportConfirmC2s}; -use valence_protocol::packet::{C2sHandshakePacket, S2cLoginPacket, S2cPlayPacket}; -use valence_protocol::var_int::VarInt; -use valence_protocol::PROTOCOL_VERSION; +use valence_core::packet::c2s::handshake::handshake::NextState; +use valence_core::packet::c2s::handshake::{C2sHandshakePacket, HandshakeC2s}; +use valence_core::packet::c2s::login::LoginHelloC2s; +use valence_core::packet::c2s::play::{KeepAliveC2s, PositionAndOnGround, TeleportConfirmC2s}; +use valence_core::packet::decode::{decode_packet, PacketDecoder}; +use valence_core::packet::encode::PacketEncoder; +use valence_core::packet::s2c::login::S2cLoginPacket; +use valence_core::packet::s2c::play::S2cPlayPacket; +use valence_core::packet::var_int::VarInt; +use valence_core::PROTOCOL_VERSION; pub struct SessionParams<'a> { pub socket_addr: SocketAddr,