Parse arguments and cleanup networking

- Parse CLI arguments using `clap`
- Or get them from browser location on web
- Support `Local`, `Host` and `Connect` modes
- Use commands for starting and connecting
- Only `spawn_initial_blocks` when server is started
- Use `autoconnect_host_client` for local servers
- Correctly handle client and server events
main
copygirl 1 day ago
parent bb66d28f06
commit 54615c265d
  1. 191
      Cargo.lock
  2. 3
      Cargo.toml
  3. 4
      client/Cargo.toml
  4. 68
      client/src/args.rs
  5. 2
      client/src/camera.rs
  6. 33
      client/src/main.rs
  7. 1
      common/Cargo.toml
  8. 1
      common/src/block.rs
  9. 47
      common/src/network/client.rs
  10. 63
      common/src/network/client_webtransport.rs
  11. 4
      common/src/network/mod.rs
  12. 75
      common/src/network/server.rs
  13. 60
      common/src/network/server_webtransport.rs

191
Cargo.lock generated

@ -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"

@ -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" ]

@ -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" ] }

@ -0,0 +1,68 @@
use clap::{Parser, Subcommand};
use common::network::{DEFAULT_ADDRESS, DEFAULT_PORT};
#[derive(Parser, Default, Debug)]
#[command(version, about)]
pub struct Args {
#[command(subcommand)]
pub mode: Option<Mode>,
}
#[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.
#[arg(default_value = DEFAULT_ADDRESS)]
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 {
<Args as Parser>::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 }),
}
}
}

@ -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));
}

@ -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<Add, Started>, mut blocks: Blocks) {
for x in -8..8 {
for z in -8..8 {
blocks.spawn(IVec3::new(x, 0, z));

@ -7,3 +7,4 @@ edition = "2024"
bevy.workspace = true
lightyear.workspace = true
serde.workspace = true
thiserror.workspace = true

@ -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),
));
}

@ -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::<lightyear::connection::client::ConnectionPlugin>() {
app.add_plugins(lightyear::connection::client::ConnectionPlugin);
}
if !app.is_plugin_added::<super::ProtocolPlugin>() {
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<Add, server::Started>, 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<Add, Connecting>) {
fn on_connecting(event: On<Add, Connecting>, clients: Query<(), With<Client>>) {
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<Add, Connected>) {
fn on_connected(event: On<Add, Connected>, clients: Query<(), With<Client>>) {
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<Remove, Connected>) {
fn on_disconnected(event: On<Remove, Connected>, clients: Query<(), With<Client>>) {
let client = event.entity;
if !clients.contains(client) {
return; // Not a client we started. (server-side?)
};
info!("Client '{client}' disconnected!");
}

@ -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<Self> {
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<SocketAddr> {
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;

@ -2,8 +2,12 @@ mod client;
mod protocol;
mod server;
mod client_webtransport;
mod server_webtransport;
pub use client::*;
pub use protocol::*;
pub use server::*;
pub const DEFAULT_PORT: u16 = 13580;
pub const DEFAULT_ADDRESS: &str = "localhost:13580";

@ -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::<lightyear::connection::client::ConnectionPlugin>() {
app.add_plugins(lightyear::connection::client::ConnectionPlugin);
}
if !app.is_plugin_added::<lightyear::connection::server::ConnectionPlugin>() {
app.add_plugins(lightyear::connection::server::ConnectionPlugin);
}
if !app.is_plugin_added::<super::ProtocolPlugin>() {
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<Add, Started>, servers: Query<&WebTransportServerIo>) -> Result {
fn on_server_started(event: On<Add, Started>) {
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<Add, Stopped>) {
@ -73,19 +52,29 @@ fn handle_client_connected(
event: On<Add, Connected>,
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<Add, Disconnected>, clients: Query<&LinkOf>) -> Result {
fn handle_client_disconnected(event: On<Remove, Connected>, 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.
}

@ -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<Add, Started>,
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(())
}
Loading…
Cancel
Save