diff --git a/Cargo.lock b/Cargo.lock index 28b4ea55..3117e307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anyhow" version = "1.0.69" @@ -317,6 +323,7 @@ dependencies = [ "azalea-registry", "bevy_ecs", "num-traits", + "serde", "uuid", ] @@ -714,9 +721,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -783,10 +790,37 @@ dependencies = [ ] [[package]] -name = "cipher" -version = "0.4.3" +name = "ciborium" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", @@ -794,13 +828,23 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "bitflags", + "clap_lex", + "indexmap", "textwrap", - "unicode-width", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -848,15 +892,16 @@ dependencies = [ [[package]] name = "criterion" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" dependencies = [ + "anes", "atty", "cast", + "ciborium", "clap", "criterion-plot", - "csv", "itertools", "lazy_static", "num-traits", @@ -865,7 +910,6 @@ dependencies = [ "rayon", "regex", "serde", - "serde_cbor", "serde_derive", "serde_json", "tinytemplate", @@ -874,9 +918,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", @@ -935,27 +979,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "csv" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "data-encoding" version = "2.3.3" @@ -1088,9 +1111,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" dependencies = [ "futures-channel", "futures-core", @@ -1103,9 +1126,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", "futures-sink", @@ -1113,15 +1136,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" dependencies = [ "futures-core", "futures-task", @@ -1130,9 +1153,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" [[package]] name = "futures-lite" @@ -1151,9 +1174,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ "proc-macro2", "quote", @@ -1162,21 +1185,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-channel", "futures-core", @@ -1330,9 +1353,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.24" +version = "0.14.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" dependencies = [ "bytes", "futures-channel", @@ -1455,9 +1478,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "linked-hash-map" @@ -1684,6 +1707,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "overload" version = "0.1.1" @@ -2045,28 +2074,18 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" -version = "1.0.153" +version = "1.0.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20" +checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.153" +version = "1.0.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f" +checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630" dependencies = [ "proc-macro2", "quote", @@ -2193,12 +2212,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.11.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" @@ -2330,9 +2346,9 @@ checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" [[package]] name = "toml_edit" -version = "0.19.4" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +checksum = "7082a95d48029677a28f181e5f6422d0c8339ad8396a39d3f33d62a90c1f6c30" dependencies = [ "indexmap", "toml_datetime", @@ -2481,9 +2497,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c" [[package]] name = "unicode-ident" @@ -2500,12 +2516,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - [[package]] name = "untrusted" version = "0.7.1" diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 65e6b9e6..0f2c7125 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -18,8 +18,8 @@ reqwest = { version = "0.11.12", default-features = false, features = [ "json", "rustls-tls", ] } -serde = { version = "1.0.145", features = ["derive"] } -serde_json = "1.0.86" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.93" thiserror = "1.0.37" tokio = { version = "1.24.2", features = ["fs"] } uuid = { version = "^1.1.2", features = ["serde"] } diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml index 067062bc..33a3aafe 100644 --- a/azalea-chat/Cargo.toml +++ b/azalea-chat/Cargo.toml @@ -18,5 +18,5 @@ azalea-buf = { path = "../azalea-buf", features = [ azalea-language = { path = "../azalea-language", version = "^0.6.0" } log = "0.4.17" once_cell = "1.16.0" -serde = { version = "^1.0.148", features = ["derive"] } -serde_json = "^1.0.72" +serde = { version = "^1.0.152", features = ["derive"] } +serde_json = "^1.0.93" diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 314295b3..f0778d8a 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -40,7 +40,7 @@ use azalea_protocol::{ }; use azalea_world::{ entity::{EntityPlugin, EntityUpdateSet, Local, WorldName}, - Instance, PartialWorld, WorldContainer, + Instance, InstanceContainer, PartialInstance, }; use bevy_app::{App, CoreSchedule, Plugin, PluginGroup, PluginGroupBuilder}; use bevy_ecs::{ @@ -77,13 +77,13 @@ pub struct Client { /// and skin data. /// /// This is immutable; the server cannot change it. To get the username and - /// skin the server chose for you, get your player from - /// [`Self::players`]. + /// skin the server chose for you, get your player from the [`TabList`] + /// component. pub profile: GameProfile, /// The entity for this client in the ECS. pub entity: Entity, /// The world that this client is in. - pub world: Arc>, + pub world: Arc>, /// The entity component system. You probably don't need to access this /// directly. Note that if you're using a shared world (i.e. a swarm), this @@ -96,8 +96,7 @@ pub struct Client { /// A component that contains some of the "settings" for this client that are /// sent to the server, such as render distance. -#[derive(Component, Clone, Debug, Deref, DerefMut, Default, Eq, PartialEq)] -pub struct ClientInformation(ServerboundClientInformationPacket); +pub type ClientInformation = ServerboundClientInformationPacket; /// A component that contains a map of player UUIDs to their information in the /// tab list. @@ -148,7 +147,7 @@ impl Client { profile, // default our id to 0, it'll be set later entity, - world: Arc::new(RwLock::new(PartialWorld::default())), + world: Arc::new(RwLock::new(PartialInstance::default())), ecs, @@ -438,14 +437,14 @@ impl Client { /// Get a reference to our (potentially shared) world. /// - /// This gets the [`World`] from our world container. If it's a normal + /// This gets the [`Instance`] from our world container. If it's a normal /// client, then it'll be the same as the world the client has loaded. /// If the client using a shared world, then the shared world will be a /// superset of the client's world. pub fn world(&self) -> Arc> { let world_name = self.component::(); let ecs = self.ecs.lock(); - let world_container = ecs.resource::(); + let world_container = ecs.resource::(); world_container.get(&world_name).unwrap() } @@ -478,7 +477,7 @@ impl Client { { let mut ecs = self.ecs.lock(); let mut client_information_mut = self.query::<&mut ClientInformation>(&mut ecs); - **client_information_mut = client_information.clone(); + *client_information_mut = client_information.clone(); } if self.logged_in() { @@ -531,7 +530,7 @@ impl Plugin for AzaleaPlugin { app.add_event::() .add_system(handle_send_packet_event); - app.init_resource::(); + app.init_resource::(); } } diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 024b8757..e9d5bd65 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -28,7 +28,9 @@ mod player; pub mod task_pool; pub use account::Account; -pub use client::{init_ecs_app, start_ecs, Client, ClientInformation, JoinError}; +pub use client::{ + init_ecs_app, start_ecs, Client, ClientInformation, JoinError, JoinedClientBundle, TabList, +}; pub use events::Event; pub use local_player::{GameProfileComponent, LocalPlayer}; pub use movement::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection}; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 4c5231dc..3481b70c 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -5,7 +5,7 @@ use azalea_core::{ChunkPos, GameMode}; use azalea_protocol::packets::game::ServerboundGamePacket; use azalea_world::{ entity::{self, Dead}, - Instance, PartialWorld, + Instance, PartialInstance, }; use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, query::Added, system::Query, @@ -33,11 +33,12 @@ use crate::{ pub struct LocalPlayer { packet_writer: mpsc::UnboundedSender, - /// The partial world is the world this client currently has loaded. It has - /// a limited render distance. - pub partial_world: Arc>, - /// The world is the combined [`PartialWorld`]s of all clients in the same - /// world. (Only relevant if you're using a shared world, i.e. a swarm) + /// The partial instance is the world this client currently has loaded. It + /// has a limited render distance. + pub partial_instance: Arc>, + /// The world is the combined [`PartialInstance`]s of all clients in the + /// same world. (Only relevant if you're using a shared world, i.e. a + /// swarm) pub world: Arc>, /// A task that reads packets from the server. The client is disconnected @@ -99,7 +100,7 @@ impl LocalPlayer { packet_writer, world, - partial_world: Arc::new(RwLock::new(PartialWorld::new( + partial_instance: Arc::new(RwLock::new(PartialInstance::new( client_information.view_distance.into(), Some(entity), ))), diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs index 348574e4..f0d49cd1 100644 --- a/azalea-client/src/packet_handling.rs +++ b/azalea-client/src/packet_handling.rs @@ -20,7 +20,7 @@ use azalea_world::{ MinecraftEntityId, Physics, PlayerBundle, Position, WorldName, }, entity::{LoadedBy, RelativeEntityUpdate}, - PartialWorld, WorldContainer, + InstanceContainer, PartialInstance, }; use bevy_app::{App, CoreSet, Plugin}; use bevy_ecs::{ @@ -193,65 +193,24 @@ fn process_packet_events(ecs: &mut World) { &GameProfileComponent, &ClientInformation, )>, - ResMut, + ResMut, )> = SystemState::new(ecs); let (mut commands, mut query, mut world_container) = system_state.get_mut(ecs); let (mut local_player, world_name, game_profile, client_information) = query.get_mut(player_entity).unwrap(); { - // TODO: have registry_holder be a struct because this sucks rn - // best way would be to add serde support to azalea-nbt - - let registry_holder = p + let dimension = &p .registry_holder - .as_compound() - .expect("Registry holder is not a compound") - .get("") - .expect("No \"\" tag") - .as_compound() - .expect("\"\" tag is not a compound"); - let dimension_types = registry_holder - .get("minecraft:dimension_type") - .expect("No dimension_type tag") - .as_compound() - .expect("dimension_type is not a compound") - .get("value") - .expect("No dimension_type value") - .as_list() - .expect("dimension_type value is not a list"); - let dimension_type = dimension_types + .root + .dimension_type + .value .iter() - .find(|t| { - t.as_compound() - .expect("dimension_type value is not a compound") - .get("name") - .expect("No name tag") - .as_string() - .expect("name is not a string") - == p.dimension_type.to_string() - }) + .find(|t| t.name == p.dimension_type) .unwrap_or_else(|| { panic!("No dimension_type with name {}", p.dimension_type) }) - .as_compound() - .unwrap() - .get("element") - .expect("No element tag") - .as_compound() - .expect("element is not a compound"); - let height = (*dimension_type - .get("height") - .expect("No height tag") - .as_int() - .expect("height tag is not an int")) - .try_into() - .expect("height is not a u32"); - let min_y = *dimension_type - .get("min_y") - .expect("No min_y tag") - .as_int() - .expect("min_y tag is not an int"); + .element; let new_world_name = p.dimension.clone(); @@ -264,12 +223,16 @@ fn process_packet_events(ecs: &mut World) { } // add this world to the world_container (or don't if it's already // there) - let weak_world = world_container.insert(new_world_name.clone(), height, min_y); + let weak_world = world_container.insert( + new_world_name.clone(), + dimension.height, + dimension.min_y, + ); // set the partial_world to an empty world // (when we add chunks or entities those will be in the // world_container) - *local_player.partial_world.write() = PartialWorld::new( + *local_player.partial_instance.write() = PartialInstance::new( client_information.view_distance.into(), // this argument makes it so other clients don't update this // player entity @@ -298,8 +261,7 @@ fn process_packet_events(ecs: &mut World) { "Sending client information because login: {:?}", client_information ); - let client_information: ClientInformation = client_information.clone(); - local_player.write_packet((*client_information).clone().get()); + local_player.write_packet(client_information.clone().get()); // brand local_player.write_packet( @@ -517,7 +479,7 @@ fn process_packet_events(ecs: &mut World) { let mut system_state: SystemState> = SystemState::new(ecs); let mut query = system_state.get_mut(ecs); let local_player = query.get_mut(player_entity).unwrap(); - let mut partial_world = local_player.partial_world.write(); + let mut partial_world = local_player.partial_instance.write(); partial_world.chunks.view_center = ChunkPos::new(p.x, p.z); } @@ -536,14 +498,14 @@ fn process_packet_events(ecs: &mut World) { // by this client. let shared_chunk = local_player.world.read().chunks.get(&pos); let this_client_has_chunk = local_player - .partial_world + .partial_instance .read() .chunks .limited_get(&pos) .is_some(); let mut world = local_player.world.write(); - let mut partial_world = local_player.partial_world.write(); + let mut partial_world = local_player.partial_instance.write(); if !this_client_has_chunk { if let Some(shared_chunk) = shared_chunk { @@ -709,7 +671,7 @@ fn process_packet_events(ecs: &mut World) { if let Some(entity) = entity { let new_position = p.position; commands.entity(entity).add(RelativeEntityUpdate { - partial_world: local_player.partial_world.clone(), + partial_world: local_player.partial_instance.clone(), update: Box::new(move |entity| { let mut position = entity.get_mut::().unwrap(); **position = new_position; @@ -740,7 +702,7 @@ fn process_packet_events(ecs: &mut World) { if let Some(entity) = entity { let delta = p.delta.clone(); commands.entity(entity).add(RelativeEntityUpdate { - partial_world: local_player.partial_world.clone(), + partial_world: local_player.partial_instance.clone(), update: Box::new(move |entity_mut| { let mut position = entity_mut.get_mut::().unwrap(); **position = position.with_delta(&delta); @@ -768,7 +730,7 @@ fn process_packet_events(ecs: &mut World) { if let Some(entity) = entity { let delta = p.delta.clone(); commands.entity(entity).add(RelativeEntityUpdate { - partial_world: local_player.partial_world.clone(), + partial_world: local_player.partial_instance.clone(), update: Box::new(move |entity_mut| { let mut position = entity_mut.get_mut::().unwrap(); **position = position.with_delta(&delta); diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index ba7477eb..c74d0214 100755 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -15,7 +15,9 @@ azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" } azalea-registry = { path = "../azalea-registry", version = "^0.6.0" } bevy_ecs = { version = "0.10.0", default-features = false, optional = true } num-traits = "0.2.15" +serde = { version = "^1.0.152", optional = true } uuid = "^1.1.2" [features] bevy_ecs = ["dep:bevy_ecs"] +serde = ["dep:serde"] diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs index 4e25d00e..09a35efd 100755 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -3,7 +3,10 @@ use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use std::io::{Cursor, Write}; -// TODO: make a `resourcelocation!("minecraft:overwolrd")` macro that checks if +#[cfg(feature = "serde")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +// TODO: make a `resourcelocation!("minecraft:overworld")` macro that checks if // it's correct at compile-time. #[derive(Hash, Clone, PartialEq, Eq)] @@ -60,6 +63,37 @@ impl McBufWritable for ResourceLocation { } } +#[cfg(feature = "serde")] +impl Serialize for ResourceLocation { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for ResourceLocation { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s.contains(':') { + match ResourceLocation::new(&s) { + Ok(r) => Ok(r), + Err(e) => Err(de::Error::custom(e)), + } + } else { + Err(de::Error::invalid_value( + de::Unexpected::Str(&s), + &"a valid ResourceLocation", + )) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/azalea-crypto/Cargo.toml b/azalea-crypto/Cargo.toml index e49e5e9d..41fcc262 100644 --- a/azalea-crypto/Cargo.toml +++ b/azalea-crypto/Cargo.toml @@ -19,7 +19,7 @@ sha-1 = "^0.10.0" uuid = "^1.1.2" [dev-dependencies] -criterion = {version = "^0.3.5", features = ["html_reports"]} +criterion = {version = "^0.4.0", features = ["html_reports"]} [[bench]] harness = false diff --git a/azalea-language/Cargo.toml b/azalea-language/Cargo.toml index d0b160c3..5d872451 100644 --- a/azalea-language/Cargo.toml +++ b/azalea-language/Cargo.toml @@ -10,6 +10,6 @@ version = "0.6.0" [dependencies] once_cell = "1.16.0" -serde = "1.0.137" -serde_json = "1.0.81" +serde = "^1.0.152" +serde_json = "^1.0.93" # tokio = {version = "^1.21.2", features = ["fs"]} diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml index b67c90c8..007da55a 100644 --- a/azalea-nbt/Cargo.toml +++ b/azalea-nbt/Cargo.toml @@ -9,17 +9,21 @@ repository = "https://github.com/mat-1/azalea/tree/main/azalea-nbt" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ahash = { version = "^0.8.0", features = ["serde"]} -azalea-buf = {path = "../azalea-buf", version = "^0.6.0" } +ahash = { version = "^0.8.3" } +azalea-buf = { path = "../azalea-buf", version = "^0.6.0" } byteorder = "^1.4.3" -flate2 = "^1.0.23" +flate2 = "^1.0.25" log = "0.4.17" num-derive = "^0.3.3" -num-traits = "^0.2.14" -serde = {version = "^1.0.148", features = ["derive"]} +num-traits = "^0.2.15" +serde = { version = "1.0.152", features = ["derive"], optional = true } [dev-dependencies] -criterion = {version = "^0.3.5", features = ["html_reports"]} +criterion = {version = "^0.4.0", features = ["html_reports"]} + +[features] +default = [] +serde = ["dep:serde", "ahash/serde"] [profile.release] lto = true diff --git a/azalea-nbt/README.md b/azalea-nbt/README.md index 5204c2d4..27de2fa6 100755 --- a/azalea-nbt/README.md +++ b/azalea-nbt/README.md @@ -7,11 +7,9 @@ A fast NBT serializer and deserializer. ``` use ahash::AHashMap; use azalea_nbt::Tag; -use std::{io::{Cursor, Read}, fs::File}; +use std::io::Cursor; -let mut file = File::open("tests/hello_world.nbt").unwrap(); -let mut buf = vec![]; -file.read_to_end(&mut buf).unwrap(); +let buf = include_bytes!("../tests/hello_world.nbt"); let tag = Tag::read(&mut Cursor::new(&buf[..])).unwrap(); assert_eq!( tag, diff --git a/azalea-nbt/src/tag.rs b/azalea-nbt/src/tag.rs index a18a42b1..87a7374d 100755 --- a/azalea-nbt/src/tag.rs +++ b/azalea-nbt/src/tag.rs @@ -1,45 +1,38 @@ use ahash::AHashMap; + +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// An NBT value. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Default)] +#[repr(u8)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))] pub enum Tag { #[default] - End, // 0 - Byte(i8), // 1 - Short(i16), // 2 - Int(i32), // 3 - Long(i64), // 4 - Float(f32), // 5 - Double(f64), // 6 - ByteArray(Vec), // 7 - String(String), // 8 - List(Vec), // 9 - Compound(AHashMap), // 10 - IntArray(Vec), // 11 - LongArray(Vec), // 12 + End = 0, + Byte(i8) = 1, + Short(i16) = 2, + Int(i32) = 3, + Long(i64) = 4, + Float(f32) = 5, + Double(f64) = 6, + ByteArray(Vec) = 7, + String(String) = 8, + List(Vec) = 9, + Compound(AHashMap) = 10, + IntArray(Vec) = 11, + LongArray(Vec) = 12, } impl Tag { /// Get the numerical ID of the tag type. #[inline] pub fn id(&self) -> u8 { - match self { - Tag::End => 0, - Tag::Byte(_) => 1, - Tag::Short(_) => 2, - Tag::Int(_) => 3, - Tag::Long(_) => 4, - Tag::Float(_) => 5, - Tag::Double(_) => 6, - Tag::ByteArray(_) => 7, - Tag::String(_) => 8, - Tag::List(_) => 9, - Tag::Compound(_) => 10, - Tag::IntArray(_) => 11, - Tag::LongArray(_) => 12, - } + // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` + // `union` between `repr(C)` structs, each of which has the `u8` + // discriminant as its first field, so we can read the discriminant + // without offsetting the pointer. + unsafe { *<*const _>::from(self).cast::() } } /// If the type is a byte, return the [`i8`]. diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs index 43c31590..c44eb818 100755 --- a/azalea-nbt/tests/tests.rs +++ b/azalea-nbt/tests/tests.rs @@ -1,16 +1,11 @@ use ahash::AHashMap; use azalea_nbt::Tag; -use std::{ - fs::File, - io::{Cursor, Read}, -}; +use std::io::Cursor; #[test] fn test_decode_hello_world() { // read hello_world.nbt - let mut file = File::open("tests/hello_world.nbt").unwrap(); - let mut buf = vec![]; - file.read_to_end(&mut buf).unwrap(); + let buf = include_bytes!("hello_world.nbt").to_vec(); let tag = Tag::read(&mut Cursor::new(&buf[..])).unwrap(); assert_eq!( tag, @@ -26,9 +21,7 @@ fn test_decode_hello_world() { #[test] fn test_roundtrip_hello_world() { - let mut file = File::open("tests/hello_world.nbt").unwrap(); - let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + let original = include_bytes!("hello_world.nbt").to_vec(); let mut original_stream = Cursor::new(&original[..]); let tag = Tag::read(&mut original_stream).unwrap(); @@ -43,9 +36,7 @@ fn test_roundtrip_hello_world() { #[test] fn test_bigtest() { // read bigtest.nbt - let mut file = File::open("tests/bigtest.nbt").unwrap(); - let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + let original = include_bytes!("bigtest.nbt").to_vec(); let mut original_stream = Cursor::new(original); let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); @@ -81,9 +72,7 @@ fn test_stringtest() { Tag::String("😁".to_string()), ]) )])); - let mut file = std::fs::File::open("tests/stringtest.nbt").unwrap(); - let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + let original = include_bytes!("stringtest.nbt").to_vec(); let mut original_stream = Cursor::new(original); let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); @@ -93,9 +82,7 @@ fn test_stringtest() { #[test] fn test_complex_player() { - let mut file = File::open("tests/complex_player.dat").unwrap(); - let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + let original = include_bytes!("complex_player.dat").to_vec(); let mut original_stream = Cursor::new(original); let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); @@ -110,9 +97,7 @@ fn test_complex_player() { #[test] fn test_simple_player() { - let mut file = File::open("tests/simple_player.dat").unwrap(); - let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + let original = include_bytes!("simple_player.dat").to_vec(); let mut original_stream = Cursor::new(original); let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 878346e8..d99e65ad 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -11,7 +11,7 @@ use azalea_world::{ metadata::Sprinting, move_relative, Attributes, Jumping, Local, LookDirection, Physics, Position, WorldName, }, - Instance, WorldContainer, + Instance, InstanceContainer, }; use bevy_app::{App, CoreSchedule, IntoSystemAppConfigs, Plugin}; use bevy_ecs::{ @@ -54,7 +54,7 @@ fn travel( ), With, >, - world_container: Res, + world_container: Res, ) { for (mut physics, direction, mut position, attributes, world_name) in &mut query { let world_lock = world_container @@ -176,7 +176,7 @@ pub fn force_jump_listener( &Sprinting, &WorldName, )>, - world_container: Res, + world_container: Res, mut events: EventReader, ) { for event in events.iter() { @@ -327,7 +327,7 @@ mod tests { use azalea_core::{ChunkPos, ResourceLocation}; use azalea_world::{ entity::{EntityBundle, EntityPlugin, MinecraftEntityId}, - Chunk, PartialWorld, + Chunk, PartialInstance, }; use bevy_app::App; use bevy_time::fixed_timestep::FixedTime; @@ -339,14 +339,14 @@ mod tests { app.add_plugin(PhysicsPlugin) .add_plugin(EntityPlugin) .insert_resource(FixedTime::new(Duration::from_millis(50))) - .init_resource::(); + .init_resource::(); app } #[test] fn test_gravity() { let mut app = make_test_app(); - let _world_lock = app.world.resource_mut::().insert( + let _world_lock = app.world.resource_mut::().insert( ResourceLocation::new("minecraft:overworld").unwrap(), 384, -64, @@ -398,12 +398,12 @@ mod tests { #[test] fn test_collision() { let mut app = make_test_app(); - let world_lock = app.world.resource_mut::().insert( + let world_lock = app.world.resource_mut::().insert( ResourceLocation::new("minecraft:overworld").unwrap(), 384, -64, ); - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); partial_world.chunks.set( &ChunkPos { x: 0, z: 0 }, @@ -457,12 +457,12 @@ mod tests { #[test] fn test_slab_collision() { let mut app = make_test_app(); - let world_lock = app.world.resource_mut::().insert( + let world_lock = app.world.resource_mut::().insert( ResourceLocation::new("minecraft:overworld").unwrap(), 384, -64, ); - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); partial_world.chunks.set( &ChunkPos { x: 0, z: 0 }, @@ -511,12 +511,12 @@ mod tests { #[test] fn test_top_slab_collision() { let mut app = make_test_app(); - let world_lock = app.world.resource_mut::().insert( + let world_lock = app.world.resource_mut::().insert( ResourceLocation::new("minecraft:overworld").unwrap(), 384, -64, ); - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); partial_world.chunks.set( &ChunkPos { x: 0, z: 0 }, @@ -564,12 +564,12 @@ mod tests { #[test] fn test_weird_wall_collision() { let mut app = make_test_app(); - let world_lock = app.world.resource_mut::().insert( + let world_lock = app.world.resource_mut::().insert( ResourceLocation::new("minecraft:overworld").unwrap(), 384, -64, ); - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); partial_world.chunks.set( &ChunkPos { x: 0, z: 0 }, @@ -622,12 +622,12 @@ mod tests { #[test] fn test_negative_coordinates_weird_wall_collision() { let mut app = make_test_app(); - let world_lock = app.world.resource_mut::().insert( + let world_lock = app.world.resource_mut::().insert( ResourceLocation::new("minecraft:overworld").unwrap(), 384, -64, ); - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); partial_world.chunks.set( &ChunkPos { x: -1, z: -1 }, diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 1bbda7eb..e9834d58 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -21,21 +21,21 @@ azalea-brigadier = { path = "../azalea-brigadier", version = "^0.6.0", features ] } azalea-buf = { path = "../azalea-buf", version = "^0.6.0" } azalea-chat = { path = "../azalea-chat", version = "^0.6.0" } -azalea-core = { path = "../azalea-core", optional = true, version = "^0.6.0" } +azalea-core = { path = "../azalea-core", optional = true, version = "^0.6.0", features = ["serde"]} azalea-crypto = { path = "../azalea-crypto", version = "^0.6.0" } -azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" } +azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0", features = ["serde"] } azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "^0.6.0" } azalea-registry = { path = "../azalea-registry", version = "^0.6.0" } azalea-world = { path = "../azalea-world", version = "^0.6.0" } bevy_ecs = { version = "0.10.0", default-features = false } byteorder = "^1.4.3" bytes = "^1.1.0" -flate2 = "1.0.23" +flate2 = "1.0.25" futures = "0.3.24" futures-util = "0.3.24" log = "0.4.17" -serde = { version = "1.0.130", features = ["serde_derive"] } -serde_json = "^1.0.72" +serde = { version = "1.0.152", features = ["serde_derive"] } +serde_json = "^1.0.93" thiserror = "1.0.37" tokio = { version = "^1.24.2", features = ["io-util", "net", "macros"] } tokio-util = { version = "0.7.4", features = ["codec"] } @@ -48,6 +48,7 @@ uuid = "1.1.2" connecting = [] default = ["packets"] packets = ["connecting", "dep:async-compression", "dep:azalea-core"] +strict_registry = ["packets"] [dev-dependencies] anyhow = "^1.0.65" diff --git a/azalea-protocol/src/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs index a21b5399..f1748073 100755 --- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs @@ -1,7 +1,12 @@ +use self::registry::RegistryHolder; use azalea_buf::McBuf; use azalea_core::{GameMode, GlobalPos, OptionalGameType, ResourceLocation}; use azalea_protocol_macros::ClientboundGamePacket; +/// The first packet sent by the server to the client after login. +/// +/// This packet contains information about the state of the player, the +/// world, and the registry. #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundLoginPacket { pub player_id: u32, @@ -9,7 +14,7 @@ pub struct ClientboundLoginPacket { pub game_type: GameMode, pub previous_game_type: OptionalGameType, pub levels: Vec, - pub registry_holder: azalea_nbt::Tag, + pub registry_holder: RegistryHolder, pub dimension_type: ResourceLocation, pub dimension: ResourceLocation, pub seed: i64, @@ -25,3 +30,445 @@ pub struct ClientboundLoginPacket { pub is_flat: bool, pub last_death_location: Option, } + +pub mod registry { + //! [ClientboundLoginPacket](super::ClientboundLoginPacket) Registry + //! Structures + //! + //! This module contains the structures used to represent the registry + //! sent to the client upon login. This contains a lot of information about + //! the game, including the types of chat messages, dimensions, and + //! biomes. + + use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; + use azalea_core::ResourceLocation; + use azalea_nbt::Tag; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + use std::{collections::HashMap, io::Cursor}; + + /// The base of the registry. + /// + /// This is the registry that is sent to the client upon login. + /// + /// As a tag, it is a compound tag that only contains a single compound tag. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct RegistryHolder { + #[serde(rename = "")] + pub root: RegistryRoot, + } + + impl TryFrom for RegistryHolder { + type Error = serde_json::Error; + + fn try_from(value: Tag) -> Result { + serde_json::from_value(serde_json::to_value(value)?) + } + } + + impl TryInto for RegistryHolder { + type Error = serde_json::Error; + + fn try_into(self) -> Result { + serde_json::from_value(serde_json::to_value(self)?) + } + } + + impl McBufReadable for RegistryHolder { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result { + RegistryHolder::try_from(Tag::read_from(buf)?) + .map_err(|e| BufReadError::Deserialization { source: e }) + } + } + + impl McBufWritable for RegistryHolder { + fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + TryInto::::try_into(self.clone())?.write_into(buf) + } + } + + /// The main part of the registry. + /// + /// The only field of [`RegistryHolder`]. + /// Contains information from the server about chat, dimensions, + /// and world generation. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct RegistryRoot { + #[serde(rename = "minecraft:chat_type")] + pub chat_type: RegistryType, + #[serde(rename = "minecraft:dimension_type")] + pub dimension_type: RegistryType, + #[serde(rename = "minecraft:worldgen/biome")] + pub world_type: RegistryType, + } + + /// A collection of values for a certain type of registry data. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct RegistryType { + #[serde(rename = "type")] + pub kind: ResourceLocation, + pub value: Vec>, + } + + /// A value for a certain type of registry data. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct TypeValue { + pub id: u32, + pub name: ResourceLocation, + pub element: T, + } + + /// Data about a kind of chat message + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct ChatTypeElement { + pub chat: ChatTypeData, + pub narration: ChatTypeData, + } + + /// Data about a chat message. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct ChatTypeData { + pub translation_key: String, + pub parameters: Vec, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub style: Option, + } + + /// The style of a chat message. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct ChatTypeStyle { + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub bold: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub italic: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub underlined: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub strikethrough: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub obfuscated: Option, + } + + /// Dimension attributes. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct DimensionTypeElement { + pub ambient_light: f32, + #[serde(with = "Convert")] + pub bed_works: bool, + pub coordinate_scale: f32, + pub effects: ResourceLocation, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub fixed_time: Option, + #[serde(with = "Convert")] + pub has_ceiling: bool, + #[serde(with = "Convert")] + pub has_raids: bool, + #[serde(with = "Convert")] + pub has_skylight: bool, + pub height: u32, + pub infiniburn: ResourceLocation, + pub logical_height: u32, + pub min_y: i32, + pub monster_spawn_block_light_limit: u32, + pub monster_spawn_light_level: MonsterSpawnLightLevel, + #[serde(with = "Convert")] + pub natural: bool, + #[serde(with = "Convert")] + pub piglin_safe: bool, + #[serde(with = "Convert")] + pub respawn_anchor_works: bool, + #[serde(with = "Convert")] + pub ultrawarm: bool, + } + + /// The light level at which monsters can spawn. + /// + /// This can be either a single minimum value, or a formula with a min and + /// max. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[serde(untagged)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub enum MonsterSpawnLightLevel { + /// A simple minimum value. + Simple(u32), + /// A complex value with a type, minimum, and maximum. + /// Vanilla minecraft only uses one type, "minecraft:uniform". + Complex { + #[serde(rename = "type")] + kind: ResourceLocation, + value: MonsterSpawnLightLevelValues, + }, + } + + /// The min and max light levels at which monsters can spawn. + /// + /// Values are inclusive. + #[derive(Debug, Copy, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct MonsterSpawnLightLevelValues { + #[serde(rename = "min_inclusive")] + pub min: u32, + #[serde(rename = "max_inclusive")] + pub max: u32, + } + + /// Biome attributes. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct WorldTypeElement { + pub temperature: f32, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub temperature_modifier: Option, + pub downfall: f32, + pub precipitation: BiomePrecipitation, + pub effects: BiomeEffects, + } + + /// The precipitation of a biome. + #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub enum BiomePrecipitation { + #[serde(rename = "none")] + None, + #[serde(rename = "rain")] + Rain, + #[serde(rename = "snow")] + Snow, + } + + /// The effects of a biome. + /// + /// This includes the sky, fog, water, and grass color, + /// as well as music and other sound effects. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct BiomeEffects { + pub sky_color: u32, + pub fog_color: u32, + pub water_color: u32, + pub water_fog_color: u32, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub foliage_color: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub grass_color: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub grass_color_modifier: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub music: Option, + pub mood_sound: BiomeMoodSound, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub additions_sound: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub ambient_sound: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub particle: Option, + } + + /// The music of the biome. + /// + /// Some biomes have unique music that only play when inside them. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct BiomeMusic { + #[serde(with = "Convert")] + pub replace_current_music: bool, + pub max_delay: u32, + pub min_delay: u32, + pub sound: SoundId, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct BiomeMoodSound { + pub tick_delay: u32, + pub block_search_extent: u32, + pub offset: f32, + pub sound: SoundId, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct AdditionsSound { + pub tick_chance: f32, + pub sound: SoundId, + } + + /// The ID of a sound. + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct SoundId { + pub sound_id: ResourceLocation, + } + + /// Biome particles. + /// + /// Some biomes have particles that spawn in the air. + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct BiomeParticle { + pub probability: f32, + pub options: HashMap, + } + + // Using a trait because you can't implement methods for + // types you don't own, in this case Option and bool. + trait Convert: Sized { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>; + } + + // Convert between bool and u8 + impl Convert for bool { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_u8(if *self { 1 } else { 0 }) + } + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + convert::(u8::deserialize(deserializer)?) + } + } + + // Convert between Option and u8 + impl Convert for Option { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if let Some(value) = self { + Convert::serialize(value, serializer) + } else { + serializer.serialize_none() + } + } + + fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + if let Some(value) = Option::::deserialize(deserializer)? { + Ok(Some(convert::(value)?)) + } else { + Ok(None) + } + } + } + + // Deserializing logic here to deduplicate code + fn convert<'de, D>(value: u8) -> Result + where + D: Deserializer<'de>, + { + match value { + 0 => Ok(false), + 1 => Ok(true), + other => Err(de::Error::invalid_value( + de::Unexpected::Unsigned(other as u64), + &"zero or one", + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::registry::{ + ChatTypeElement, DimensionTypeElement, RegistryHolder, RegistryRoot, RegistryType, + WorldTypeElement, + }; + use azalea_core::ResourceLocation; + use azalea_nbt::Tag; + + #[test] + fn test_convert() { + let registry = RegistryHolder { + root: RegistryRoot { + chat_type: RegistryType:: { + kind: ResourceLocation::new("minecraft:chat_type").unwrap(), + value: Vec::new(), + }, + dimension_type: RegistryType:: { + kind: ResourceLocation::new("minecraft:dimension_type").unwrap(), + value: Vec::new(), + }, + world_type: RegistryType:: { + kind: ResourceLocation::new("minecraft:worldgen/biome").unwrap(), + value: Vec::new(), + }, + }, + }; + + let tag: Tag = registry.try_into().unwrap(); + let root = tag + .as_compound() + .unwrap() + .get("") + .unwrap() + .as_compound() + .unwrap(); + + let chat = root + .get("minecraft:chat_type") + .unwrap() + .as_compound() + .unwrap(); + let chat_type = chat.get("type").unwrap().as_string().unwrap(); + assert!(chat_type == "minecraft:chat_type"); + + let dimension = root + .get("minecraft:dimension_type") + .unwrap() + .as_compound() + .unwrap(); + let dimension_type = dimension.get("type").unwrap().as_string().unwrap(); + assert!(dimension_type == "minecraft:dimension_type"); + + let world = root + .get("minecraft:worldgen/biome") + .unwrap() + .as_compound() + .unwrap(); + let world_type = world.get("type").unwrap().as_string().unwrap(); + assert!(world_type == "minecraft:worldgen/biome"); + } +} diff --git a/azalea-protocol/src/packets/game/serverbound_client_information_packet.rs b/azalea-protocol/src/packets/game/serverbound_client_information_packet.rs index 65e13239..9cace991 100755 --- a/azalea-protocol/src/packets/game/serverbound_client_information_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_client_information_packet.rs @@ -1,8 +1,9 @@ use azalea_buf::{McBuf, McBufReadable, McBufWritable}; use azalea_core::FixedBitSet; use azalea_protocol_macros::ServerboundGamePacket; +use bevy_ecs::component::Component; -#[derive(Clone, Debug, McBuf, ServerboundGamePacket, PartialEq, Eq)] +#[derive(Clone, Debug, McBuf, ServerboundGamePacket, PartialEq, Eq, Component)] pub struct ServerboundClientInformationPacket { /// The locale of the client. pub language: String, diff --git a/azalea-protocol/src/resolver.rs b/azalea-protocol/src/resolver.rs index d12654f2..f99cec05 100755 --- a/azalea-protocol/src/resolver.rs +++ b/azalea-protocol/src/resolver.rs @@ -50,6 +50,15 @@ pub async fn resolve_address(address: &ServerAddress) -> Result>>, } -impl WorldContainer { +impl InstanceContainer { pub fn new() -> Self { - WorldContainer { + InstanceContainer { worlds: HashMap::new(), } } diff --git a/azalea-world/src/entity/info.rs b/azalea-world/src/entity/info.rs index cb59a4a8..3d9aceb5 100644 --- a/azalea-world/src/entity/info.rs +++ b/azalea-world/src/entity/info.rs @@ -6,7 +6,7 @@ use crate::{ entity::{ self, add_dead, update_bounding_box, EntityUuid, MinecraftEntityId, Position, WorldName, }, - update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer, + update_entity_by_id_index, update_uuid_index, InstanceContainer, PartialInstance, }; use azalea_core::ChunkPos; use bevy_app::{App, CoreSet, Plugin}; @@ -135,9 +135,9 @@ impl PartialEntityInfos { } } -/// A [`Command`] that applies a "relative update" to an entity, which means -/// this update won't be run multiple times by different clients in the same -/// world. +/// An [`EntityCommand`] that applies a "relative update" to an entity, which +/// means this update won't be run multiple times by different clients in the +/// same world. /// /// This is used to avoid a bug where when there's multiple clients in the same /// world and an entity sends a relative move packet to all clients, its @@ -147,7 +147,7 @@ impl PartialEntityInfos { /// other clients within render distance will get too. You usually don't need /// this when the change isn't relative either. pub struct RelativeEntityUpdate { - pub partial_world: Arc>, + pub partial_world: Arc>, // a function that takes the entity and updates it pub update: Box, } @@ -219,7 +219,7 @@ fn update_entity_chunk_positions( ), Changed, >, - world_container: Res, + world_container: Res, ) { for (entity, pos, last_pos, world_name) in query.iter_mut() { let world_lock = world_container.get(world_name).unwrap(); @@ -286,7 +286,7 @@ fn debug_detect_updates_received_on_local_entities( fn remove_despawned_entities_from_indexes( mut commands: Commands, mut entity_infos: ResMut, - world_container: Res, + world_container: Res, query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed>, ) { for (entity, uuid, position, world_name, loaded_by) in &query { diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs index 802659f7..857daf88 100644 --- a/azalea-world/src/entity/mod.rs +++ b/azalea-world/src/entity/mod.rs @@ -264,7 +264,7 @@ pub struct EyeHeight(f32); /// Most of the time, you should be using `azalea_registry::EntityKind` /// directly instead. #[derive(Component, Clone, Copy, Debug, PartialEq, Deref)] -pub struct EntityKind(azalea_registry::EntityKind); +pub struct EntityKind(pub azalea_registry::EntityKind); /// A bundle of components that every entity has. This doesn't contain metadata, /// that has to be added separately. diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 7da9b869..e8d25032 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -4,7 +4,7 @@ use crate::{ }, iterators::ChunkIterator, palette::Palette, - ChunkStorage, PartialChunkStorage, WorldContainer, + ChunkStorage, InstanceContainer, PartialChunkStorage, }; use azalea_block::{BlockState, BlockStates}; use azalea_core::{BlockPos, ChunkPos}; @@ -21,24 +21,24 @@ use std::{ fmt::Debug, }; -/// PartialWorlds are usually owned by clients, and hold strong references to -/// chunks and entities in [`World`]s. +/// PartialInstances are usually owned by clients, and hold strong references to +/// chunks and entities in [`Instance`]s. /// /// Basically, they hold the chunks and entities that are within render /// distance but can still access chunks and entities owned by other -/// `PartialWorld`s that have the same `World`. +/// `PartialInstance`s that have the same `Instance`. /// -/// This is primarily useful for having multiple clients in the same world. -pub struct PartialWorld { +/// This is primarily useful for having multiple clients in the same Instance. +pub struct PartialInstance { pub chunks: PartialChunkStorage, /// Some metadata about entities, like what entities are in certain chunks. /// This does not contain the entity data itself, that's in the ECS. pub entity_infos: PartialEntityInfos, } -impl PartialWorld { +impl PartialInstance { pub fn new(chunk_radius: u32, owner_entity: Option) -> Self { - PartialWorld { + PartialInstance { chunks: PartialChunkStorage::new(chunk_radius), entity_infos: PartialEntityInfos::new(owner_entity), } @@ -59,7 +59,7 @@ pub fn deduplicate_entities( (Changed, Without), >, mut loaded_by_query: Query<&mut LoadedBy>, - world_container: Res, + world_container: Res, ) { // if this entity already exists, remove it for (new_entity, id, world_name) in query.iter_mut() { @@ -104,7 +104,7 @@ pub fn deduplicate_local_entities( (Entity, &MinecraftEntityId, &WorldName), (Changed, With), >, - world_container: Res, + world_container: Res, ) { // if this entity already exists, remove the old one for (new_entity, id, world_name) in query.iter_mut() { @@ -262,7 +262,7 @@ impl Instance { } } -impl Debug for PartialWorld { +impl Debug for PartialInstance { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("World") .field("chunk_storage", &self.chunks) @@ -271,8 +271,8 @@ impl Debug for PartialWorld { } } -impl Default for PartialWorld { - /// Creates a completely self-contained `PartialWorld`. This is only for +impl Default for PartialInstance { + /// Creates a completely self-contained `PartialInstance`. This is only for /// testing and shouldn't be used in actual code! fn default() -> Self { let chunk_storage = PartialChunkStorage::default(); @@ -290,7 +290,7 @@ pub fn update_entity_by_id_index( (Entity, &MinecraftEntityId, &WorldName, Option<&Local>), Changed, >, - world_container: Res, + world_container: Res, ) { for (entity, id, world_name, local) in query.iter_mut() { let world_lock = world_container.get(world_name).unwrap(); diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 61a92038..9c06ebb8 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -20,7 +20,7 @@ use azalea_world::entity::metadata::Player; use azalea_world::entity::Local; use azalea_world::{ entity::{Physics, Position, WorldName}, - WorldContainer, + InstanceContainer, }; use bevy_tasks::{AsyncComputeTaskPool, Task}; use futures_lite::future; @@ -93,7 +93,7 @@ fn goto_listener( mut commands: Commands, mut events: EventReader, mut query: Query<(&Position, &WorldName)>, - world_container: Res, + world_container: Res, ) { let thread_pool = AsyncComputeTaskPool::get(); diff --git a/azalea/src/pathfinder/moves.rs b/azalea/src/pathfinder/moves.rs index 3639c091..6625581d 100644 --- a/azalea/src/pathfinder/moves.rs +++ b/azalea/src/pathfinder/moves.rs @@ -151,11 +151,11 @@ mod tests { use super::*; use azalea_block::BlockState; use azalea_core::ChunkPos; - use azalea_world::{Chunk, ChunkStorage, PartialWorld}; + use azalea_world::{Chunk, ChunkStorage, PartialInstance}; #[test] fn test_is_passable() { - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); let mut chunk_storage = ChunkStorage::default(); partial_world.chunks.set( @@ -181,7 +181,7 @@ mod tests { #[test] fn test_is_solid() { - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); let mut chunk_storage = ChunkStorage::default(); partial_world.chunks.set( &ChunkPos { x: 0, z: 0 }, @@ -206,7 +206,7 @@ mod tests { #[test] fn test_is_standable() { - let mut partial_world = PartialWorld::default(); + let mut partial_world = PartialInstance::default(); let mut chunk_storage = ChunkStorage::default(); partial_world.chunks.set( &ChunkPos { x: 0, z: 0 }, diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 97020153..6fe11b7d 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -11,7 +11,7 @@ use azalea_protocol::{ resolver::{self, ResolverError}, ServerAddress, }; -use azalea_world::WorldContainer; +use azalea_world::InstanceContainer; use bevy_app::{App, Plugin, PluginGroup, PluginGroupBuilder}; use bevy_ecs::{component::Component, entity::Entity, system::Resource, world::World}; use futures::future::join_all; @@ -24,7 +24,7 @@ use tokio::sync::mpsc; /// A swarm is a way to conveniently control many bots at once, while also /// being able to control bots at an individual level when desired. /// -/// Swarms are created from [`azalea::swarm::SwarmBuilder`]. +/// Swarms are created from [`SwarmBuilder`]. /// /// The `S` type parameter is the type of the state for individual bots. /// It's used to make the [`Swarm::add`] function work. @@ -37,7 +37,7 @@ pub struct Swarm { // bot_datas: Arc>>, resolved_address: SocketAddr, address: ServerAddress, - pub world_container: Arc>, + pub world_container: Arc>, bots_tx: mpsc::UnboundedSender<(Option, Client)>, swarm_tx: mpsc::UnboundedSender, @@ -248,7 +248,7 @@ where // resolve the address let resolved_address = resolver::resolve_address(&address).await?; - let world_container = Arc::new(RwLock::new(WorldContainer::default())); + let world_container = Arc::new(RwLock::new(InstanceContainer::default())); // we can't modify the swarm plugins after this let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();