diff --git a/Cargo.lock b/Cargo.lock index b2fd896..2bf31ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,6 +281,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -579,8 +629,10 @@ dependencies = [ "bevy", "bevy-bloxel-classic-common", "bevy_fix_cursor_unlock_web", + "clap", "lightyear", "serde", + "web-sys", ] [[package]] @@ -590,6 +642,7 @@ dependencies = [ "bevy", "lightyear", "serde", + "thiserror 2.0.17", ] [[package]] @@ -2138,6 +2191,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "cmake" version = "0.1.54" @@ -2158,6 +2251,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.7" @@ -3441,6 +3540,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -3697,6 +3802,12 @@ dependencies = [ "mach2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -3880,7 +3991,7 @@ dependencies = [ [[package]] name = "lightyear" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "bevy_app", @@ -3922,7 +4033,7 @@ dependencies = [ [[package]] name = "lightyear_aeronet" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "bevy_app", @@ -3936,7 +4047,7 @@ dependencies = [ [[package]] name = "lightyear_avian2d" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "avian2d", "bevy_app", @@ -3957,7 +4068,7 @@ dependencies = [ [[package]] name = "lightyear_avian3d" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "avian3d", "bevy_app", @@ -3978,7 +4089,7 @@ dependencies = [ [[package]] name = "lightyear_connection" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -3997,7 +4108,7 @@ dependencies = [ [[package]] name = "lightyear_core" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_derive", @@ -4015,7 +4126,7 @@ dependencies = [ [[package]] name = "lightyear_frame_interpolation" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -4034,7 +4145,7 @@ dependencies = [ [[package]] name = "lightyear_inputs" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -4057,7 +4168,7 @@ dependencies = [ [[package]] name = "lightyear_inputs_bei" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -4077,7 +4188,7 @@ dependencies = [ [[package]] name = "lightyear_inputs_leafwing" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_derive", @@ -4097,7 +4208,7 @@ dependencies = [ [[package]] name = "lightyear_inputs_native" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_derive", @@ -4112,7 +4223,7 @@ dependencies = [ [[package]] name = "lightyear_interpolation" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_derive", @@ -4136,7 +4247,7 @@ dependencies = [ [[package]] name = "lightyear_link" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -4152,7 +4263,7 @@ dependencies = [ [[package]] name = "lightyear_messages" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -4173,7 +4284,7 @@ dependencies = [ [[package]] name = "lightyear_netcode" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "bevy_app", @@ -4198,7 +4309,7 @@ dependencies = [ [[package]] name = "lightyear_prediction" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_derive", @@ -4226,28 +4337,22 @@ dependencies = [ [[package]] name = "lightyear_raw_connection" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "bevy_app", "bevy_ecs", - "bevy_platform", - "bevy_reflect", - "bytes", "lightyear_connection", "lightyear_core", "lightyear_link", - "lightyear_serde", - "serde", - "smallvec", - "thiserror 2.0.17", + "lightyear_transport", "tracing", ] [[package]] name = "lightyear_replication" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "avian2d", "avian3d", @@ -4282,7 +4387,7 @@ dependencies = [ [[package]] name = "lightyear_serde" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_derive", "bevy_ecs", @@ -4302,7 +4407,7 @@ dependencies = [ [[package]] name = "lightyear_steam" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "aeronet_steam", @@ -4319,7 +4424,7 @@ dependencies = [ [[package]] name = "lightyear_sync" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_derive", @@ -4342,7 +4447,7 @@ dependencies = [ [[package]] name = "lightyear_transport" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_ecs", @@ -4370,7 +4475,7 @@ dependencies = [ [[package]] name = "lightyear_udp" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "bevy_app", @@ -4386,7 +4491,7 @@ dependencies = [ [[package]] name = "lightyear_ui" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_app", "bevy_color", @@ -4405,7 +4510,7 @@ dependencies = [ [[package]] name = "lightyear_utils" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "bevy_ecs", "bevy_platform", @@ -4420,7 +4525,7 @@ dependencies = [ [[package]] name = "lightyear_websocket" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "aeronet_websocket", @@ -4436,7 +4541,7 @@ dependencies = [ [[package]] name = "lightyear_webtransport" version = "0.25.3" -source = "git+https://github.com/cBournhonesque/lightyear.git?rev=5559dd47a014040f570516983ace2c9e9a25ac89#5559dd47a014040f570516983ace2c9e9a25ac89" +source = "git+https://github.com/cBournhonesque/lightyear.git?rev=f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa#f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" dependencies = [ "aeronet_io", "aeronet_webtransport", @@ -5220,6 +5325,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oneshot" version = "0.1.11" @@ -6493,6 +6604,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -7072,6 +7189,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.18.1" diff --git a/Cargo.toml b/Cargo.toml index 317e181..d7304f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,10 @@ opt-level = 3 bevy = { version = "0.17.2", features = [ "serialize" ] } # lightyear = { version = "0.25.3", features = [ "netcode", "webtransport" ] } serde = "1.0.228" +thiserror = "2.0.17" # TODO: Once lightyear releases a version with `raw_connection` support, switch to that. [workspace.dependencies.lightyear] git = "https://github.com/cBournhonesque/lightyear.git" -rev = "5559dd47a014040f570516983ace2c9e9a25ac89" +rev = "f8583d6630a72bdb8bebb4083a8f3f9cc20aecaa" features = [ "raw_connection", "webtransport" ] diff --git a/client/Cargo.toml b/client/Cargo.toml index f09f375..e0a9daf 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "bevy-bloxel-classic" +description = "A multiplayer bloxel sandbox game inspired by Minecraft Classic (2009)" version = "0.1.0" edition = "2024" @@ -11,3 +12,6 @@ lightyear.workspace = true serde.workspace = true bevy_fix_cursor_unlock_web = "0.2.0" + +clap = { version = "4.5.50", features = [ "derive" ] } +web-sys = { version = "0.3.81", features = [ "Location", "UrlSearchParams", "Window" ] } diff --git a/client/src/args.rs b/client/src/args.rs new file mode 100644 index 0000000..9663345 --- /dev/null +++ b/client/src/args.rs @@ -0,0 +1,67 @@ +use clap::{Parser, Subcommand}; +use common::network::DEFAULT_PORT; + +#[derive(Parser, Default, Debug)] +#[command(version, about)] +pub struct Args { + #[command(subcommand)] + pub mode: Option, +} + +#[derive(Subcommand, Debug)] +pub enum Mode { + /// Play the game on your own. + Local, + /// Host a server and play on it. + #[cfg(not(target_family = "wasm"))] + Host { + #[arg(default_value_t = DEFAULT_PORT)] + port: u16, + }, + /// Connect to an existing server. + Connect { + /// Address to connect to. + address: String, + /// Certificate digest provided by the server. + digest: String, + }, +} + +impl Default for Mode { + fn default() -> Self { + Self::Local + } +} + +impl Args { + #[cfg(not(target_family = "wasm"))] + pub fn parse() -> Self { + ::parse() + } + + #[cfg(target_family = "wasm")] + pub fn parse() -> Self { + use bevy::log::error; + use web_sys::{UrlSearchParams, window}; + + let Some(window) = window() else { + return Self::default(); + }; + let Ok(search) = window.location().search() else { + return Self::default(); + }; + let Ok(params) = UrlSearchParams::new_with_str(&search) else { + return Self::default(); + }; + let Some(address) = params.get("connect") else { + return Self::default(); + }; + let Some(digest) = params.get("digest") else { + error!("Missing 'digest' parameter."); + return Self::default(); + }; + Self { + mode: Some(Mode::Connect { address, digest }), + } + } +} diff --git a/client/src/camera.rs b/client/src/camera.rs index 12de9e4..e5f8a01 100644 --- a/client/src/camera.rs +++ b/client/src/camera.rs @@ -43,7 +43,7 @@ pub fn cursor_grab( // On the web, the cursor can be locked, but setting its position is // not supported at all, so this would instead log a bunch of errors. let center = window.resolution.size() / 2.; - #[cfg(not(target_arch = "wasm32"))] // skip on web + #[cfg(not(target_family = "wasm"))] // skip on web window.set_cursor_position(Some(center)); } diff --git a/client/src/main.rs b/client/src/main.rs index d14325d..8e3faa0 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,17 +1,22 @@ use bevy::prelude::*; +use common::network::*; use bevy::window::WindowResolution; +use lightyear::prelude::server::Started; +mod args; mod block; mod camera; mod placement; +use args::*; use block::*; use camera::*; use placement::*; #[rustfmt::skip] -fn main() { +fn main() -> Result { + let cli = Args::parse(); let mut app = App::new(); app.add_plugins(DefaultPlugins.set(WindowPlugin { @@ -32,6 +37,9 @@ fn main() { // Fixes issue on web where the cursor isn't ungrabbed properly. app.add_plugins(bevy_fix_cursor_unlock_web::FixPointerUnlockPlugin); + app.add_plugins(common::network::ServerPlugin); + app.add_plugins(common::network::ClientPlugin); + app.add_systems(Startup, setup_crosshair); app.add_systems(Startup, setup_blocks); app.add_systems(Startup, setup_scene.after(setup_blocks)); @@ -45,19 +53,24 @@ fn main() { // For a most up-to-date value, run it after that's been updated. app.add_systems(PostUpdate, place_break_blocks.after(TransformSystems::Propagate)); + app.add_observer(spawn_initial_blocks); app.add_observer(add_block_visuals); - // FIXME: Don't hardcode this! - #[cfg(not(target_arch = "wasm32"))] - app.add_plugins(common::network::ServerPlugin); - #[cfg(target_arch = "wasm32")] - app.add_plugins(common::network::ClientPlugin); + let mut commands = app.world_mut().commands(); + match cli.mode.unwrap_or_default() { + Mode::Local => commands.queue(StartLocalServerCommand), + #[cfg(not(target_family = "wasm"))] + Mode::Host { port } => commands.queue(StartWebTransportServerCommand::new(port)), + Mode::Connect { address, digest } => commands.queue(ConnectWebTransportCommand::new(&address, digest)?), + } + // NOTE: When setting up a local server, a host-client is automatically + // connected to it thanks to the `autoconnect_host_client` observer. app.run(); + Ok(()) } -fn setup_scene(mut commands: Commands, mut blocks: Blocks) { - // light +fn setup_scene(mut commands: Commands) { commands.spawn(( PointLight { shadows_enabled: true, @@ -66,15 +79,15 @@ fn setup_scene(mut commands: Commands, mut blocks: Blocks) { Transform::from_xyz(4.0, 8.0, 4.0), )); - // camera commands.spawn(( Camera3d::default(), Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), CameraFreeLook::default(), CameraNoClip::default(), )); +} - // blocks +fn spawn_initial_blocks(_event: On, mut blocks: Blocks) { for x in -8..8 { for z in -8..8 { blocks.spawn(IVec3::new(x, 0, z)); diff --git a/common/Cargo.toml b/common/Cargo.toml index 9e52b04..bdd41e3 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" bevy.workspace = true lightyear.workspace = true serde.workspace = true +thiserror.workspace = true diff --git a/common/src/block.rs b/common/src/block.rs index cf1f061..8001c02 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -18,7 +18,6 @@ impl Blocks<'_, '_> { self.commands.spawn(( Block, Transform::from_translation(pos.as_vec3() + Vec3::ONE / 2.), - // Currently prints some warnings when no `Server` is active, but this is fine. Replicate::to_clients(NetworkTarget::All), )); } diff --git a/common/src/network/client.rs b/common/src/network/client.rs index 7c47f20..3d39acb 100644 --- a/common/src/network/client.rs +++ b/common/src/network/client.rs @@ -1,63 +1,60 @@ -use std::net::{Ipv4Addr, SocketAddr}; - use bevy::prelude::*; use lightyear::prelude::client::*; use lightyear::prelude::*; -// FIXME: Don't hardcode this! -pub const DIGEST: &'static str = ""; +pub use super::client_webtransport::ConnectWebTransportCommand; pub struct ClientPlugin; impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.add_plugins(ClientPlugins::default()); - // This maybe should be added by `ClientPlugins` but it currently isn't. - // (Unless we're using `NetcodeClientPlugin`, which would've added it.) - if !app.is_plugin_added::() { - app.add_plugins(lightyear::connection::client::ConnectionPlugin); - } if !app.is_plugin_added::() { app.add_plugins(super::ProtocolPlugin); } - app.add_systems(Startup, connect_to_server); - app.add_observer(on_connecting); app.add_observer(on_connected); app.add_observer(on_disconnected); + + app.add_observer(autoconnect_host_client); } } -fn connect_to_server(mut commands: Commands) { - let client_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); - let server_addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), super::DEFAULT_PORT); - let certificate_digest = DIGEST.to_string(); - +/// Automatically creates a "host client" to connect to the local server when it is started. +fn autoconnect_host_client(event: On, mut commands: Commands) { + let server = event.entity; commands .spawn(( - Name::from("Client"), - LocalAddr(client_addr), - PeerAddr(server_addr), + Client::default(), + Name::from("HostClient"), ReplicationReceiver::default(), - WebTransportClientIo { certificate_digest }, - RawClient, + LinkOf { server }, )) - .trigger(|entity| LinkStart { entity }); + .trigger(|entity| Connect { entity }); } -fn on_connecting(event: On) { +fn on_connecting(event: On, clients: Query<(), With>) { let client = event.entity; + if !clients.contains(client) { + return; // Not a client we started. (server-side?) + }; info!("Client '{client}' connecting ..."); } -fn on_connected(event: On) { +fn on_connected(event: On, clients: Query<(), With>) { let client = event.entity; + if !clients.contains(client) { + return; // Not a client we started. (server-side?) + }; info!("Client '{client}' connected!"); } -fn on_disconnected(event: On) { +fn on_disconnected(event: On, clients: Query<(), With>) { let client = event.entity; + if !clients.contains(client) { + return; // Not a client we started. (server-side?) + }; info!("Client '{client}' disconnected!"); } diff --git a/common/src/network/client_webtransport.rs b/common/src/network/client_webtransport.rs new file mode 100644 index 0000000..f0d38c2 --- /dev/null +++ b/common/src/network/client_webtransport.rs @@ -0,0 +1,63 @@ +use std::net::{Ipv4Addr, SocketAddr, ToSocketAddrs}; + +use bevy::prelude::*; +use lightyear::prelude::client::*; +use lightyear::prelude::*; + +use thiserror::Error; + +pub struct ConnectWebTransportCommand { + server_addr: SocketAddr, + certificate_digest: String, +} + +impl ConnectWebTransportCommand { + pub fn new(server_addr: &str, certificate_digest: String) -> Result { + let server_addr = to_socket_addr_with_default_port(server_addr, super::DEFAULT_PORT)?; + Ok(Self { + server_addr, + certificate_digest, + }) + } +} + +impl Command for ConnectWebTransportCommand { + fn apply(self, world: &mut World) -> () { + let client_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); + let certificate_digest = self.certificate_digest; + world + .spawn(( + Client::default(), + Name::from("Client"), + LocalAddr(client_addr), + PeerAddr(self.server_addr), + ReplicationReceiver::default(), + WebTransportClientIo { certificate_digest }, + RawClient, + )) + .trigger(|entity| Connect { entity }); + } +} + +// TODO: Move this to its own `utils` module? +// FIXME: Hostname resolving does not work on web. +fn to_socket_addr_with_default_port(addr: &str, default_port: u16) -> Result { + let has_port = match (addr.rfind(']'), addr.rfind(':')) { + // This doesn't match colons within IPv6 brackets, like `[::1]`. + (Some(bracket), Some(colon)) if bracket < colon => true, + (None, Some(_)) => true, + _ => false, + }; + + let mut socket_addrs = if has_port { + addr.to_socket_addrs() + } else { + (addr, default_port).to_socket_addrs() + }?; + + socket_addrs.next().ok_or(ResolveError.into()) +} + +#[derive(Error, Debug)] +#[error("hostname could not be resolved to any address")] +pub struct ResolveError; diff --git a/common/src/network/mod.rs b/common/src/network/mod.rs index faed515..c621989 100644 --- a/common/src/network/mod.rs +++ b/common/src/network/mod.rs @@ -2,6 +2,9 @@ mod client; mod protocol; mod server; +mod client_webtransport; +mod server_webtransport; + pub use client::*; pub use protocol::*; pub use server::*; diff --git a/common/src/network/server.rs b/common/src/network/server.rs index b5bda75..c52881d 100644 --- a/common/src/network/server.rs +++ b/common/src/network/server.rs @@ -1,67 +1,46 @@ -use std::net::{Ipv4Addr, SocketAddr}; - use bevy::prelude::*; use lightyear::prelude::server::*; use lightyear::prelude::*; +#[cfg(not(target_family = "wasm"))] +pub use super::server_webtransport::StartWebTransportServerCommand; + pub struct ServerPlugin; impl Plugin for ServerPlugin { fn build(&self, app: &mut App) { app.add_plugins(ServerPlugins::default()); - // These maybe should be added by `ServerPlugins` but currently aren't. - // (Unless we're using `NetcodeServerPlugin`, which would've added it.) - if !app.is_plugin_added::() { - app.add_plugins(lightyear::connection::client::ConnectionPlugin); - } - if !app.is_plugin_added::() { - app.add_plugins(lightyear::connection::server::ConnectionPlugin); - } if !app.is_plugin_added::() { app.add_plugins(super::ProtocolPlugin); } - app.add_systems(Startup, start_server); - // TODO: See what happens when we try to start a server, but it // can't, for example due to the port already being in use. app.add_observer(on_server_started); app.add_observer(on_server_stopped); app.add_observer(handle_client_connected); app.add_observer(handle_client_disconnected); + + #[cfg(not(target_family = "wasm"))] + app.add_observer(super::server_webtransport::print_certificate_digest); } } -fn start_server(mut commands: Commands) -> Result { - let server_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), super::DEFAULT_PORT); - let certificate = Identity::self_signed(["localhost", "127.0.0.1", "::1"])?; +/// Starts a local-only server that only a host-client can connect to. +pub struct StartLocalServerCommand; - commands - .spawn(( - Name::from("Server"), - LocalAddr(server_addr), - WebTransportServerIo { certificate }, - RawServer, - )) - .trigger(|entity| LinkStart { entity }); - - Ok(()) +impl Command for StartLocalServerCommand { + fn apply(self, world: &mut World) { + world + .spawn((Server::default(), Name::from("Server"), RawServer)) + .trigger(|entity| Start { entity }); + } } -fn on_server_started(event: On, servers: Query<&WebTransportServerIo>) -> Result { +fn on_server_started(event: On) { let server = event.entity; info!("Server '{server}' started!"); - - let certificate = &servers.get(server)?.certificate; - let certificate_hash = certificate.certificate_chain().as_slice()[0].hash(); - let certificate_digest = certificate_hash.to_string().replace(':', ""); - - info!("== Certificate Digest =="); - info!(" Clients use this to securely connect to the server."); - info!(" {certificate_digest}"); - - Ok(()) } fn on_server_stopped(event: On) { @@ -73,19 +52,29 @@ fn handle_client_connected( event: On, clients: Query<&LinkOf>, mut commands: Commands, -) -> Result { +) { let client = event.entity; - let server = clients.get(client)?.server; + let Ok(LinkOf { server }) = clients.get(client) else { + return; // Not a client of the server. (client-side?) + }; + info!("Client '{client}' connected to server '{server}'"); - commands.entity(client).insert(ReplicationSender::default()); + commands + .entity(client) + .insert_if_new(Name::from("RemoteClient")) + .insert(ReplicationSender::default()); - Ok(()) + // TODO: Spawn player entity. } -fn handle_client_disconnected(event: On, clients: Query<&LinkOf>) -> Result { +fn handle_client_disconnected(event: On, clients: Query<&LinkOf>) { let client = event.entity; - let server = clients.get(client)?.server; + let Ok(LinkOf { server }) = clients.get(client) else { + return; // Not a client of the server. (client-side?) + }; + info!("Client '{client}' disconnected from server '{server}'"); - Ok(()) + + // TODO: Despawn player entity. } diff --git a/common/src/network/server_webtransport.rs b/common/src/network/server_webtransport.rs new file mode 100644 index 0000000..edd18ae --- /dev/null +++ b/common/src/network/server_webtransport.rs @@ -0,0 +1,60 @@ +#![cfg(not(target_family = "wasm"))] + +use std::net::{Ipv4Addr, SocketAddr}; + +use bevy::prelude::*; +use lightyear::prelude::server::*; +use lightyear::prelude::*; + +pub struct StartWebTransportServerCommand { + server_addr: SocketAddr, + certificate: Identity, +} + +impl StartWebTransportServerCommand { + pub fn new(port: u16) -> StartWebTransportServerCommand { + Self { + // TODO: Allow specifying this? + server_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port), + certificate: Identity::self_signed(["localhost", "127.0.0.1", "::1"]).unwrap(), + } + } +} + +impl Default for StartWebTransportServerCommand { + fn default() -> Self { + Self::new(super::DEFAULT_PORT) + } +} + +impl Command for StartWebTransportServerCommand { + fn apply(self, world: &mut World) { + world + .spawn(( + Server::default(), + Name::from("Server"), + LocalAddr(self.server_addr), + WebTransportServerIo { + certificate: self.certificate, + }, + RawServer, + )) + .trigger(|entity| Start { entity }); + } +} + +pub(crate) fn print_certificate_digest( + event: On, + servers: Query<&WebTransportServerIo>, +) -> Result { + let server = event.entity; + let certificate = &servers.get(server)?.certificate; + let certificate_hash = certificate.certificate_chain().as_slice()[0].hash(); + let certificate_digest = certificate_hash.to_string().replace(':', ""); + + info!("== Certificate Digest =="); + info!(" {certificate_digest}"); + info!(" (Clients use this to securely connect to the server.)"); + + Ok(()) +}