Compare commits
20 Commits
4fb37e27c5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ed54dfdc7 | |||
|
|
dd01b03526 | ||
|
|
da5c0f8fb7 | ||
| 375b8a5b46 | |||
| 89e6102b06 | |||
| e22fa8d134 | |||
| 181b617620 | |||
| 56ca801992 | |||
| 16cd95ae02 | |||
| fcb13eed31 | |||
|
|
0735c429ca | ||
|
|
c3c5ae6dfb | ||
|
|
cc7e2aae70 | ||
|
|
dbcd822b50 | ||
| ac8c834f2f | |||
| f35275ab9f | |||
| 3901ee1174 | |||
|
|
7d280af821 | ||
| 7ea9046414 | |||
|
|
f6fa9ce1e4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,3 @@ build/steamos/hedz_reloaded
|
||||
build/steamos/.env
|
||||
build/macos/src/HEDZReloaded.app/Contents/MacOS
|
||||
build/macos/src/Applications
|
||||
server.log
|
||||
386
Cargo.lock
generated
386
Cargo.lock
generated
@@ -102,6 +102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
@@ -512,6 +513,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy-persistent"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e58d92d32bb99fa22ed46aeabe5212f5d1bc8952ebf9c49b5271fc06a1359f8"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"gloo-storage",
|
||||
"ron 0.11.0",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy-steamworks"
|
||||
version = "0.15.0"
|
||||
@@ -567,7 +581,7 @@ dependencies = [
|
||||
"bevy_utils",
|
||||
"blake3",
|
||||
"derive_more",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"either",
|
||||
"petgraph",
|
||||
"ron 0.10.1",
|
||||
@@ -627,7 +641,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"console_error_panic_hook",
|
||||
"ctrlc",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"log",
|
||||
"thiserror 2.0.17",
|
||||
"variadics_please",
|
||||
@@ -658,7 +672,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"derive_more",
|
||||
"disqualified",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"either",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
@@ -730,6 +744,7 @@ dependencies = [
|
||||
"bevy_reflect",
|
||||
"bevy_transform",
|
||||
"coreaudio-sys",
|
||||
"cpal",
|
||||
"rodio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -761,7 +776,7 @@ dependencies = [
|
||||
"bevy_utils",
|
||||
"bevy_window",
|
||||
"derive_more",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 2.0.17",
|
||||
@@ -1164,6 +1179,7 @@ dependencies = [
|
||||
"bevy_pbr",
|
||||
"bevy_picking",
|
||||
"bevy_platform",
|
||||
"bevy_post_process",
|
||||
"bevy_ptr",
|
||||
"bevy_reflect",
|
||||
"bevy_render",
|
||||
@@ -1359,25 +1375,6 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_pkv"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "356a9c6fdc13faf7897103b43a8b84aafe24e1bbf1599df1fb00dc4e9b7055db"
|
||||
dependencies = [
|
||||
"bevy_app",
|
||||
"bevy_ecs",
|
||||
"cfg_aliases",
|
||||
"directories",
|
||||
"redb",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_platform"
|
||||
version = "0.17.3"
|
||||
@@ -1399,6 +1396,36 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_post_process"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b857972f5d56b43b0dce2c843b75b64d5fbbd0f6177f6ecccd75e7e41f72deb"
|
||||
dependencies = [
|
||||
"bevy_app",
|
||||
"bevy_asset",
|
||||
"bevy_camera",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_derive",
|
||||
"bevy_ecs",
|
||||
"bevy_image",
|
||||
"bevy_math",
|
||||
"bevy_platform",
|
||||
"bevy_reflect",
|
||||
"bevy_render",
|
||||
"bevy_shader",
|
||||
"bevy_transform",
|
||||
"bevy_utils",
|
||||
"bevy_window",
|
||||
"bitflags 2.10.0",
|
||||
"nonmax",
|
||||
"radsort",
|
||||
"smallvec",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_ptr"
|
||||
version = "0.17.3"
|
||||
@@ -1418,7 +1445,7 @@ dependencies = [
|
||||
"bevy_utils",
|
||||
"derive_more",
|
||||
"disqualified",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"erased-serde",
|
||||
"foldhash 0.2.0",
|
||||
"glam 0.30.9",
|
||||
@@ -1477,7 +1504,7 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytemuck",
|
||||
"derive_more",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"encase",
|
||||
"fixedbitset",
|
||||
"image",
|
||||
@@ -1519,6 +1546,7 @@ dependencies = [
|
||||
"bevy_time",
|
||||
"renet",
|
||||
"renet_netcode",
|
||||
"renet_steam",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2126,6 +2154,18 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"rustix 0.38.44",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
@@ -2780,10 +2820,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11277822c27bde750de02c5dc5159b91e88bf2661a2c1d98106f2fb1c5c6f590"
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
@@ -2859,6 +2899,12 @@ dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "2.0.2"
|
||||
@@ -3472,6 +3518,34 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-storage"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.16.0"
|
||||
@@ -3611,7 +3685,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "happy_feet"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rustunit/happy_feet.git?rev=919657fa#919657fa3330b3a78026c239c17122b3e3beefd7"
|
||||
source = "git+https://github.com/PROMETHIA-27/happy_feet.git?rev=e3a4660e0b68f9bf4d6facb0fb4d93f2d7848b27#e3a4660e0b68f9bf4d6facb0fb4d93f2d7848b27"
|
||||
dependencies = [
|
||||
"avian3d",
|
||||
"bevy",
|
||||
@@ -3680,18 +3754,19 @@ dependencies = [
|
||||
"avian3d",
|
||||
"bevy",
|
||||
"bevy-inspector-egui",
|
||||
"bevy-persistent",
|
||||
"bevy-steamworks",
|
||||
"bevy_asset_loader",
|
||||
"bevy_ballistic",
|
||||
"bevy_common_assets",
|
||||
"bevy_debug_log",
|
||||
"bevy_pkv",
|
||||
"bevy_replicon",
|
||||
"bevy_replicon_renet",
|
||||
"bevy_sprite3d",
|
||||
"bevy_trenchbroom",
|
||||
"bevy_trenchbroom_avian",
|
||||
"clap",
|
||||
"dirs",
|
||||
"happy_feet",
|
||||
"nil 0.14.0",
|
||||
"rand 0.8.5",
|
||||
@@ -4991,7 +5066,7 @@ dependencies = [
|
||||
"approx",
|
||||
"arrayvec",
|
||||
"bitflags 2.10.0",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"either",
|
||||
"ena",
|
||||
"foldhash 0.2.0",
|
||||
@@ -5022,7 +5097,7 @@ dependencies = [
|
||||
"approx",
|
||||
"arrayvec",
|
||||
"bitflags 2.10.0",
|
||||
"downcast-rs",
|
||||
"downcast-rs 2.0.2",
|
||||
"either",
|
||||
"ena",
|
||||
"foldhash 0.2.0",
|
||||
@@ -5308,6 +5383,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
@@ -5458,15 +5542,6 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb"
|
||||
|
||||
[[package]]
|
||||
name = "redb"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
@@ -5564,6 +5639,18 @@ dependencies = [
|
||||
"renetcode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "renet_steam"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f6018afe469d3d2d49fab8fd1cecc46c588ac61498cad879d5781c44b277421"
|
||||
dependencies = [
|
||||
"bevy_ecs",
|
||||
"log",
|
||||
"renet",
|
||||
"steamworks",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "renetcode"
|
||||
version = "1.0.0"
|
||||
@@ -5574,28 +5661,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "robust"
|
||||
version = "1.2.0"
|
||||
@@ -5770,12 +5835,31 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sctk-adwaita"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"log",
|
||||
"memmap2",
|
||||
"smithay-client-toolkit",
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "self_cell"
|
||||
version = "1.2.1"
|
||||
@@ -5940,6 +6024,31 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2",
|
||||
"rustix 0.38.44",
|
||||
"thiserror 1.0.69",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.2.2"
|
||||
@@ -6019,6 +6128,12 @@ version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42862065c9e685d08cc3d9f6c609d4b46bd9684ec7e9420688eb979213469582"
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -6238,6 +6353,31 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
@@ -6712,6 +6852,114 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs 1.2.1",
|
||||
"rustix 1.1.2",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"rustix 1.1.2",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-csd-frame"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cursor-icon",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29"
|
||||
dependencies = [
|
||||
"rustix 1.1.2",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-plasma"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.83"
|
||||
@@ -7390,6 +7638,7 @@ version = "0.30.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.10.0",
|
||||
@@ -7404,6 +7653,7 @@ dependencies = [
|
||||
"dpi",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"memmap2",
|
||||
"ndk 0.9.0",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
@@ -7415,11 +7665,17 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.44",
|
||||
"sctk-adwaita",
|
||||
"smithay-client-toolkit",
|
||||
"smol_str",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-plasma",
|
||||
"web-sys",
|
||||
"web-time",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -7490,6 +7746,12 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon-dl"
|
||||
version = "0.4.2"
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -51,24 +51,23 @@ bevy = { version = "0.17.0", default-features = false, features = [
|
||||
"track_location",
|
||||
] }
|
||||
bevy-inspector-egui = "0.34"
|
||||
bevy-persistent = { version = "0.9", features = ["ron"] }
|
||||
bevy-steamworks = "0.15.0"
|
||||
bevy_asset_loader = "=0.24.0-rc.1"
|
||||
bevy_ballistic = { git = "https://github.com/rustunit/bevy_ballistic.git", rev = "b08ffec" }
|
||||
bevy_common_assets = { version = "0.14.0", features = ["ron"] }
|
||||
bevy_debug_log = { git = "https://github.com/rustunit/bevy_debug_log.git", rev = "86051a0" }
|
||||
bevy_pkv = { version = "0.14", default-features = false, features = [
|
||||
"bevy",
|
||||
"redb",
|
||||
] }
|
||||
bevy_replicon = "0.37.1"
|
||||
bevy_replicon_renet = "0.13.0"
|
||||
# TODO: i dont think we need this in dedicated server mode
|
||||
bevy_replicon_renet = { version = "0.13.0", features = ["renet_steam"] }
|
||||
bevy_sprite3d = "7.0.0"
|
||||
bevy_trenchbroom = { version = "0.10", default-features = false, features = [
|
||||
"physics-integration",
|
||||
] }
|
||||
bevy_trenchbroom_avian = "0.10"
|
||||
clap = { version = "=4.5.47", features = ["derive"] }
|
||||
happy_feet = { git = "https://github.com/rustunit/happy_feet.git", rev = "919657fa", features = [
|
||||
dirs = "6.0.0"
|
||||
happy_feet = { git = "https://github.com/PROMETHIA-27/happy_feet.git", rev = "e3a4660e0b68f9bf4d6facb0fb4d93f2d7848b27", features = [
|
||||
"serde",
|
||||
] }
|
||||
nil = "0.14.0"
|
||||
@@ -76,6 +75,7 @@ rand = "=0.8.5"
|
||||
ron = "0.8"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
shared = { path = "crates/shared" }
|
||||
# TODO: i dont think we need this in dedicated server mode
|
||||
steamworks = "0.12"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
|
||||
Binary file not shown.
@@ -20,24 +20,25 @@ client = [
|
||||
"bevy_replicon_renet/client",
|
||||
"bevy_trenchbroom/client",
|
||||
]
|
||||
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
|
||||
dbg = ["avian3d/debug-plugin", "bevy/debug", "dep:bevy-inspector-egui"]
|
||||
|
||||
[dependencies]
|
||||
avian3d = { workspace = true }
|
||||
bevy = { workspace = true }
|
||||
bevy-inspector-egui = { workspace = true, optional = true }
|
||||
bevy-persistent = { workspace = true }
|
||||
bevy-steamworks = { workspace = true }
|
||||
bevy_asset_loader = { workspace = true }
|
||||
bevy_ballistic = { workspace = true }
|
||||
bevy_common_assets = { workspace = true }
|
||||
bevy_debug_log = { workspace = true }
|
||||
bevy_pkv = { workspace = true }
|
||||
bevy_replicon = { workspace = true }
|
||||
bevy_replicon_renet = { workspace = true }
|
||||
bevy_sprite3d = { workspace = true }
|
||||
bevy_trenchbroom = { workspace = true }
|
||||
bevy_trenchbroom_avian = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
happy_feet = { workspace = true }
|
||||
nil = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use super::TriggerArrow;
|
||||
use crate::{
|
||||
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase,
|
||||
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer,
|
||||
utils::sprite_3d_animation::AnimationTimer,
|
||||
GameState, global_observer,
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::Hit,
|
||||
loading_assets::GameAssets,
|
||||
physics_layers::GameLayer,
|
||||
utils::{Billboard, sprite_3d_animation::AnimationTimer},
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use super::TriggerGun;
|
||||
use crate::{
|
||||
GameState, abilities::ProjectileId, billboards::Billboard, global_observer,
|
||||
heads_database::HeadsDatabase, hitpoints::Hit, loading_assets::GameAssets,
|
||||
physics_layers::GameLayer, tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
|
||||
GameState,
|
||||
abilities::ProjectileId,
|
||||
global_observer,
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::Hit,
|
||||
loading_assets::GameAssets,
|
||||
physics_layers::GameLayer,
|
||||
tb_entities::EnemySpawn,
|
||||
utils::{Billboard, sprite_3d_animation::AnimationTimer},
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
physics_layers::GameLayer,
|
||||
player::Player,
|
||||
protocol::PlaySound,
|
||||
utils::{billboards::Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer},
|
||||
utils::{Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer},
|
||||
};
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, Signature, ToClients};
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
mod marker;
|
||||
mod target_ui;
|
||||
|
||||
use crate::{
|
||||
GameState, control::Inputs, head::ActiveHead, heads_database::HeadsDatabase,
|
||||
hitpoints::Hitpoints, physics_layers::GameLayer, player::Player, tb_entities::EnemySpawn,
|
||||
GameState,
|
||||
control::Inputs,
|
||||
head::ActiveHead,
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::Hitpoints,
|
||||
physics_layers::GameLayer,
|
||||
player::{LocalPlayer, Player},
|
||||
tb_entities::EnemySpawn,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use marker::MarkerEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
@@ -21,7 +23,12 @@ pub struct AimTarget(pub Option<Entity>);
|
||||
pub struct AimState {
|
||||
pub range: f32,
|
||||
pub max_angle: f32,
|
||||
pub spawn_marker: bool,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub enum MarkerEvent {
|
||||
Spawn(Entity),
|
||||
Despawn,
|
||||
}
|
||||
|
||||
impl Default for AimState {
|
||||
@@ -29,7 +36,6 @@ impl Default for AimState {
|
||||
Self {
|
||||
range: 80.,
|
||||
max_angle: PI / 8.,
|
||||
spawn_marker: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,20 +44,12 @@ pub fn plugin(app: &mut App) {
|
||||
app.register_type::<AimState>();
|
||||
app.register_type::<AimTarget>();
|
||||
|
||||
app.add_plugins(target_ui::plugin);
|
||||
app.add_plugins(marker::plugin);
|
||||
app.register_required_components::<ActiveHead, AimState>();
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
(update_player_aim, update_npc_aim, head_change).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
app.add_systems(Update, add_aim);
|
||||
}
|
||||
|
||||
fn add_aim(mut commands: Commands, query: Query<Entity, Added<ActiveHead>>) {
|
||||
for e in query.iter() {
|
||||
commands.entity(e).insert(AimState::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn head_change(
|
||||
@@ -70,12 +68,19 @@ fn update_player_aim(
|
||||
mut commands: Commands,
|
||||
potential_targets: Query<(Entity, &Transform), With<Hitpoints>>,
|
||||
mut player_aim: Query<
|
||||
(Entity, &AimState, &mut AimTarget, &GlobalTransform, &Inputs),
|
||||
(
|
||||
Entity,
|
||||
&AimState,
|
||||
&mut AimTarget,
|
||||
&GlobalTransform,
|
||||
&Inputs,
|
||||
Has<LocalPlayer>,
|
||||
),
|
||||
With<Player>,
|
||||
>,
|
||||
spatial_query: SpatialQuery,
|
||||
) {
|
||||
for (player, state, mut aim_target, global_tf, inputs) in player_aim.iter_mut() {
|
||||
for (player, state, mut aim_target, global_tf, inputs, is_local) in player_aim.iter_mut() {
|
||||
let (player_pos, player_forward) = (global_tf.translation(), inputs.look_dir);
|
||||
|
||||
let mut new_target = None;
|
||||
@@ -114,13 +119,14 @@ fn update_player_aim(
|
||||
}
|
||||
|
||||
if new_target != aim_target.0 {
|
||||
if state.spawn_marker {
|
||||
if is_local {
|
||||
if let Some(target) = new_target {
|
||||
commands.trigger(MarkerEvent::Spawn(target));
|
||||
} else {
|
||||
commands.trigger(MarkerEvent::Despawn);
|
||||
}
|
||||
}
|
||||
|
||||
aim_target.0 = new_target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
use super::UiHeadState;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub static BACKPACK_HEAD_SLOTS: usize = 5;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackMarker;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackCountText;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadSelector(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadImage(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadDamage(pub usize);
|
||||
|
||||
#[derive(Component, Default, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct BackpackUiState {
|
||||
pub heads: [Option<UiHeadState>; 5],
|
||||
pub scroll: usize,
|
||||
pub count: usize,
|
||||
pub current_slot: usize,
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl BackpackUiState {
|
||||
pub fn relative_current_slot(&self) -> usize {
|
||||
self.current_slot.saturating_sub(self.scroll)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<BackpackUiState>();
|
||||
}
|
||||
@@ -6,13 +6,7 @@ use crate::{
|
||||
heads_database::HeadsDatabase,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
#[cfg(feature = "client")]
|
||||
use bevy_replicon::prelude::ClientTriggerExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use ui_head_state::UiHeadState;
|
||||
|
||||
pub mod backpack_ui;
|
||||
pub mod ui_head_state;
|
||||
|
||||
#[derive(Component, Default, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
@@ -40,120 +34,15 @@ impl Backpack {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Serialize, Deserialize)]
|
||||
#[derive(Event, Debug, Serialize, Deserialize)]
|
||||
pub struct BackpackSwapEvent(pub usize);
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<Backpack>();
|
||||
|
||||
app.add_plugins(backpack_ui::plugin);
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
app.add_systems(FixedUpdate, (backpack_inputs, sync_on_change));
|
||||
|
||||
global_observer!(app, on_head_collect);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn backpack_inputs(
|
||||
backpacks: Single<
|
||||
(&Backpack, &mut backpack_ui::BackpackUiState),
|
||||
With<crate::player::LocalPlayer>,
|
||||
>,
|
||||
mut backpack_inputs: MessageReader<crate::control::BackpackButtonPress>,
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
use crate::{control::BackpackButtonPress, protocol::PlaySound};
|
||||
|
||||
let (backpack, mut state) = backpacks.into_inner();
|
||||
|
||||
for input in backpack_inputs.read() {
|
||||
match input {
|
||||
BackpackButtonPress::Toggle => {
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
}
|
||||
BackpackButtonPress::Swap => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
commands.client_trigger(BackpackSwapEvent(state.current_slot));
|
||||
}
|
||||
BackpackButtonPress::Left => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot > 0 {
|
||||
state.current_slot -= 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
BackpackButtonPress::Right => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot < state.count.saturating_sub(1) {
|
||||
state.current_slot += 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn sync_on_change(
|
||||
backpack: Query<Ref<Backpack>>,
|
||||
mut state: Single<&mut backpack_ui::BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for backpack in backpack.iter() {
|
||||
if backpack.is_changed() || backpack.reloading() {
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn sync_backpack_ui(backpack: &Backpack, state: &mut backpack_ui::BackpackUiState, time: f32) {
|
||||
use crate::backpack::backpack_ui::BACKPACK_HEAD_SLOTS;
|
||||
|
||||
state.count = backpack.heads.len();
|
||||
|
||||
state.scroll = state
|
||||
.scroll
|
||||
.min(state.count.saturating_sub(BACKPACK_HEAD_SLOTS));
|
||||
|
||||
if state.current_slot >= state.scroll + BACKPACK_HEAD_SLOTS {
|
||||
state.scroll = state.current_slot.saturating_sub(BACKPACK_HEAD_SLOTS - 1);
|
||||
}
|
||||
if state.current_slot < state.scroll {
|
||||
state.scroll = state.current_slot;
|
||||
}
|
||||
|
||||
for i in 0..BACKPACK_HEAD_SLOTS {
|
||||
if let Some(head) = backpack.heads.get(i + state.scroll) {
|
||||
use crate::backpack::ui_head_state::UiHeadState;
|
||||
|
||||
state.heads[i] = Some(UiHeadState::new(*head, time));
|
||||
} else {
|
||||
state.heads[i] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_head_collect(
|
||||
trigger: On<HeadCollected>,
|
||||
mut cmds: Commands,
|
||||
@@ -165,7 +54,7 @@ fn on_head_collect(
|
||||
let (mut backpack, active_heads) = query.get_mut(entity)?;
|
||||
|
||||
if backpack.contains(head) || active_heads.contains(head) {
|
||||
cmds.trigger(CashCollectEvent);
|
||||
cmds.trigger(CashCollectEvent { entity });
|
||||
} else {
|
||||
backpack.insert(head, heads_db.as_ref());
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
use crate::heads::HeadState;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Default, Serialize, Deserialize)]
|
||||
pub struct UiHeadState {
|
||||
pub head: usize,
|
||||
pub health: f32,
|
||||
pub ammo: f32,
|
||||
pub reloading: Option<f32>,
|
||||
}
|
||||
|
||||
impl UiHeadState {
|
||||
pub fn damage(&self) -> f32 {
|
||||
1. - self.health
|
||||
}
|
||||
|
||||
pub fn ammo_used(&self) -> f32 {
|
||||
1. - self.ammo
|
||||
}
|
||||
|
||||
pub fn reloading(&self) -> Option<f32> {
|
||||
self.reloading
|
||||
}
|
||||
|
||||
pub fn new(value: HeadState, time: f32) -> Self {
|
||||
let reloading = if value.has_ammo() {
|
||||
None
|
||||
} else {
|
||||
Some((time - value.last_use) / value.reload_duration)
|
||||
};
|
||||
|
||||
Self {
|
||||
head: value.head,
|
||||
ammo: value.ammo as f32 / value.ammo_max as f32,
|
||||
health: value.health as f32 / value.health_max as f32,
|
||||
reloading,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,3 @@
|
||||
use crate::GameState;
|
||||
#[cfg(feature = "client")]
|
||||
use crate::control::Inputs;
|
||||
#[cfg(feature = "client")]
|
||||
use crate::physics_layers::GameLayer;
|
||||
#[cfg(feature = "client")]
|
||||
use crate::player::LocalPlayer;
|
||||
#[cfg(feature = "client")]
|
||||
use crate::{control::LookDirMovement, loading_assets::UIAssets};
|
||||
#[cfg(feature = "client")]
|
||||
use avian3d::prelude::SpatialQuery;
|
||||
#[cfg(feature = "client")]
|
||||
use avian3d::prelude::{
|
||||
Collider, LayerMask, PhysicsLayer as _, ShapeCastConfig, SpatialQueryFilter,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -21,173 +6,3 @@ pub struct CameraTarget;
|
||||
|
||||
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CameraArmRotation;
|
||||
|
||||
/// Requested camera rotation based on various input sources (keyboard, gamepad)
|
||||
#[derive(Component, Reflect, Debug, Default, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
pub struct CameraRotationInput(pub Vec2);
|
||||
|
||||
#[derive(Resource, Reflect, Debug, Default)]
|
||||
#[reflect(Resource)]
|
||||
pub struct CameraState {
|
||||
pub cutscene: bool,
|
||||
pub look_around: bool,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Debug, Default)]
|
||||
struct CameraUi;
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct MainCamera {
|
||||
pub enabled: bool,
|
||||
dir: Dir3,
|
||||
distance: f32,
|
||||
target_offset: Vec3,
|
||||
}
|
||||
|
||||
impl MainCamera {
|
||||
fn new(arm: Vec3) -> Self {
|
||||
let (dir, distance) = Dir3::new_and_length(arm).expect("invalid arm length");
|
||||
Self {
|
||||
enabled: true,
|
||||
dir,
|
||||
distance,
|
||||
target_offset: Vec3::new(0., 2., 0.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<CameraRotationInput>();
|
||||
app.register_type::<CameraState>();
|
||||
app.register_type::<MainCamera>();
|
||||
|
||||
app.init_resource::<CameraState>();
|
||||
app.add_systems(OnEnter(GameState::Playing), startup);
|
||||
#[cfg(feature = "client")]
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
(update, update_ui, update_look_around, rotate_view).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
|
||||
fn startup(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
MainCamera::new(Vec3::new(0., 1.8, 15.)),
|
||||
CameraRotationInput::default(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update_look_around(
|
||||
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||
mut cam_state: ResMut<CameraState>,
|
||||
) {
|
||||
let look_around = inputs.view_mode;
|
||||
|
||||
if look_around != cam_state.look_around {
|
||||
cam_state.look_around = look_around;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update_ui(
|
||||
mut commands: Commands,
|
||||
cam_state: Res<CameraState>,
|
||||
assets: Res<UIAssets>,
|
||||
query: Query<Entity, With<CameraUi>>,
|
||||
) {
|
||||
if cam_state.is_changed() {
|
||||
let show_ui = cam_state.look_around || cam_state.cutscene;
|
||||
|
||||
if show_ui {
|
||||
commands.spawn((
|
||||
CameraUi,
|
||||
Node {
|
||||
margin: UiRect::top(Val::Px(20.))
|
||||
.with_left(Val::Auto)
|
||||
.with_right(Val::Auto),
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
children![(
|
||||
Node {
|
||||
display: Display::Block,
|
||||
position_type: PositionType::Absolute,
|
||||
..default()
|
||||
},
|
||||
ImageNode::new(assets.camera.clone()),
|
||||
)],
|
||||
));
|
||||
} else {
|
||||
for entity in query.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update(
|
||||
mut cam: Query<
|
||||
(&MainCamera, &mut Transform, &CameraRotationInput),
|
||||
(Without<CameraTarget>, Without<CameraArmRotation>),
|
||||
>,
|
||||
target_q: Single<&Transform, (With<CameraTarget>, Without<CameraArmRotation>)>,
|
||||
arm_rotation: Single<&Transform, With<CameraArmRotation>>,
|
||||
spatial_query: SpatialQuery,
|
||||
cam_state: Res<CameraState>,
|
||||
) {
|
||||
if cam_state.cutscene {
|
||||
return;
|
||||
}
|
||||
|
||||
let arm_tf = arm_rotation;
|
||||
|
||||
let Ok((camera, mut cam_transform, cam_rotation_input)) = cam.single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !camera.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = target_q.translation + camera.target_offset;
|
||||
|
||||
let direction = arm_tf.rotation * Quat::from_rotation_y(cam_rotation_input.x) * camera.dir;
|
||||
|
||||
let max_distance = camera.distance;
|
||||
|
||||
let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits()));
|
||||
let cam_pos = if let Some(first_hit) = spatial_query.cast_shape(
|
||||
&Collider::sphere(0.5),
|
||||
target,
|
||||
Quat::IDENTITY,
|
||||
direction,
|
||||
&ShapeCastConfig::from_max_distance(max_distance),
|
||||
&filter,
|
||||
) {
|
||||
let distance = first_hit.distance;
|
||||
target + (direction * distance)
|
||||
} else {
|
||||
target + (direction * camera.distance)
|
||||
};
|
||||
|
||||
*cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn rotate_view(
|
||||
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||
look_dir: Res<LookDirMovement>,
|
||||
mut cam: Single<&mut CameraRotationInput>,
|
||||
) {
|
||||
if !inputs.view_mode {
|
||||
cam.x = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
cam.0 += look_dir.0 * -0.001;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use crate::{
|
||||
GameState, HEDZ_GREEN, global_observer, loading_assets::UIAssets, protocol::PlaySound,
|
||||
GameState, global_observer,
|
||||
physics_layers::GameLayer,
|
||||
player::Player,
|
||||
protocol::{GltfSceneRoot, PlaySound, is_server},
|
||||
server_observer,
|
||||
tb_entities::CashSpawn,
|
||||
};
|
||||
use avian3d::prelude::Rotation;
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[derive(Component, Reflect, Default, Deserialize, Serialize)]
|
||||
#[reflect(Component)]
|
||||
#[require(Transform)]
|
||||
pub struct Cash;
|
||||
@@ -20,32 +25,64 @@ pub struct CashInventory {
|
||||
pub cash: i32,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct CashCollectEvent;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(rotate, update_ui).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
server_observer!(app, on_cash_collect);
|
||||
#[derive(EntityEvent)]
|
||||
pub struct CashCollectEvent {
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
fn on_cash_collect(
|
||||
_trigger: On<CashCollectEvent>,
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), setup.run_if(is_server));
|
||||
app.add_systems(Update, rotate.run_if(in_state(GameState::Playing)));
|
||||
|
||||
server_observer!(app, on_cash_collected);
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, query: Query<Entity, With<CashSpawn>>) {
|
||||
for entity in query.iter() {
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert((
|
||||
Name::new("cash"),
|
||||
GltfSceneRoot::Cash,
|
||||
Cash,
|
||||
Collider::cuboid(2., 3.0, 2.),
|
||||
CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::ALL),
|
||||
RigidBody::Kinematic,
|
||||
CollisionEventsEnabled,
|
||||
Sensor,
|
||||
Replicated,
|
||||
))
|
||||
.observe(on_cash_collision);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_cash_collected(
|
||||
trigger: On<CashCollectEvent>,
|
||||
mut commands: Commands,
|
||||
mut cash: Single<&mut CashInventory>,
|
||||
mut query_player: Query<&mut CashInventory, With<Player>>,
|
||||
) {
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||
if let Ok(mut cash) = query_player.get_mut(trigger.entity) {
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::CashCollect,
|
||||
});
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::CashCollect,
|
||||
});
|
||||
cash.cash += 100;
|
||||
}
|
||||
}
|
||||
|
||||
cash.cash += 100;
|
||||
fn on_cash_collision(
|
||||
trigger: On<CollisionStart>,
|
||||
mut commands: Commands,
|
||||
query_player: Query<&Player>,
|
||||
) {
|
||||
let collectable = trigger.event().collider1;
|
||||
let collider = trigger.event().collider2;
|
||||
|
||||
if query_player.contains(collider) {
|
||||
commands.trigger(CashCollectEvent { entity: collider });
|
||||
commands.entity(collectable).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate(time: Res<Time>, mut query: Query<&mut Rotation, With<Cash>>) {
|
||||
@@ -55,37 +92,3 @@ fn rotate(time: Res<Time>, mut query: Query<&mut Rotation, With<Cash>>) {
|
||||
.mul_quat(Quat::from_rotation_y(time.delta_secs()));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_ui(
|
||||
cash: Single<&CashInventory, Changed<CashInventory>>,
|
||||
text: Query<Entity, With<CashText>>,
|
||||
mut writer: TextUiWriter,
|
||||
) {
|
||||
let Some(text) = text.iter().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
*writer.text(text, 0) = cash.cash.to_string();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||
commands.spawn((
|
||||
Name::new("cash-ui"),
|
||||
Text::new("0"),
|
||||
TextShadow::default(),
|
||||
CashText,
|
||||
TextFont {
|
||||
font: assets.font.clone(),
|
||||
font_size: 34.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(HEDZ_GREEN.into()),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
bottom: Val::Px(40.0),
|
||||
left: Val::Px(100.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
8
crates/hedz_reloaded/src/client/aim.rs
Normal file
8
crates/hedz_reloaded/src/client/aim.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub mod marker;
|
||||
pub mod target_ui;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins((marker::plugin, target_ui::plugin));
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{GameState, global_observer, loading_assets::UIAssets, utils::billboards::Billboard};
|
||||
use crate::{
|
||||
GameState, aim::MarkerEvent, global_observer, loading_assets::UIAssets, utils::Billboard,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_sprite3d::Sprite3d;
|
||||
use ops::sin;
|
||||
@@ -7,12 +9,6 @@ use ops::sin;
|
||||
#[reflect(Component)]
|
||||
struct TargetMarker;
|
||||
|
||||
#[derive(Event)]
|
||||
pub enum MarkerEvent {
|
||||
Spawn(Entity),
|
||||
Despawn,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Update, move_marker.run_if(in_state(GameState::Playing)));
|
||||
global_observer!(app, marker_event);
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::AimTarget;
|
||||
use crate::{
|
||||
GameState,
|
||||
backpack::UiHeadState,
|
||||
heads::{ActiveHeads, HeadsImages},
|
||||
aim::AimTarget,
|
||||
client::ui::{HeadsImages, UiHeadState},
|
||||
heads::ActiveHeads,
|
||||
hitpoints::Hitpoints,
|
||||
loading_assets::UIAssets,
|
||||
npc::Npc,
|
||||
player::Player,
|
||||
player::LocalPlayer,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -18,12 +18,14 @@ struct HeadImage;
|
||||
#[reflect(Component)]
|
||||
struct HeadDamage;
|
||||
|
||||
#[derive(Resource, Default, PartialEq)]
|
||||
struct TargetUi {
|
||||
#[derive(Component, Default, PartialEq)]
|
||||
pub struct TargetUi {
|
||||
head: Option<UiHeadState>,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_required_components::<LocalPlayer, TargetUi>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(Update, (sync, update).run_if(in_state(GameState::Playing)));
|
||||
}
|
||||
@@ -44,8 +46,6 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||
assets.head_damage.clone(),
|
||||
)],
|
||||
));
|
||||
|
||||
commands.insert_resource(TargetUi::default());
|
||||
}
|
||||
|
||||
fn spawn_head_ui(bg: Handle<Image>, regular: Handle<Image>, damage: Handle<Image>) -> impl Bundle {
|
||||
@@ -110,7 +110,7 @@ fn spawn_head_ui(bg: Handle<Image>, regular: Handle<Image>, damage: Handle<Image
|
||||
}
|
||||
|
||||
fn update(
|
||||
target: Res<TargetUi>,
|
||||
target: Single<&TargetUi, (Changed<TargetUi>, With<LocalPlayer>)>,
|
||||
heads_images: Res<HeadsImages>,
|
||||
mut head_image: Query<
|
||||
(&mut Visibility, &mut ImageNode),
|
||||
@@ -118,30 +118,28 @@ fn update(
|
||||
>,
|
||||
mut head_damage: Query<&mut Node, (With<HeadDamage>, Without<HeadImage>)>,
|
||||
) {
|
||||
if target.is_changed() {
|
||||
if let Ok((mut vis, mut image)) = head_image.single_mut() {
|
||||
if let Some(head) = target.head {
|
||||
*vis = Visibility::Visible;
|
||||
image.image = heads_images.heads[head.head].clone();
|
||||
} else {
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
if let Ok((mut vis, mut image)) = head_image.single_mut() {
|
||||
if let Some(head) = target.head {
|
||||
*vis = Visibility::Visible;
|
||||
image.image = heads_images.heads[head.head].clone();
|
||||
} else {
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut node) = head_damage.single_mut() {
|
||||
node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.);
|
||||
}
|
||||
if let Ok(mut node) = head_damage.single_mut() {
|
||||
node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.);
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(
|
||||
mut target: ResMut<TargetUi>,
|
||||
player_target: Query<&AimTarget, With<Player>>,
|
||||
mut target: Single<&mut TargetUi, With<LocalPlayer>>,
|
||||
player_target: Single<&AimTarget, With<LocalPlayer>>,
|
||||
target_data: Query<(&Hitpoints, &ActiveHeads), With<Npc>>,
|
||||
) {
|
||||
let mut new_state = None;
|
||||
if let Some(e) = player_target.iter().next().and_then(|target| target.0)
|
||||
&& let Ok((hp, heads)) = target_data.get(e)
|
||||
if let Some(target) = player_target.0
|
||||
&& let Ok((hp, heads)) = target_data.get(target)
|
||||
{
|
||||
let head = heads.current().expect("target must have a head on");
|
||||
new_state = Some(UiHeadState {
|
||||
98
crates/hedz_reloaded/src/client/backpack.rs
Normal file
98
crates/hedz_reloaded/src/client/backpack.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::{
|
||||
backpack::{Backpack, BackpackSwapEvent},
|
||||
client::ui::{BACKPACK_HEAD_SLOTS, BackpackUiState, UiHeadState},
|
||||
control::BackpackButtonPress,
|
||||
player::LocalPlayer,
|
||||
protocol::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::prelude::ClientTriggerExt;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(FixedUpdate, (backpack_inputs, sync_on_change));
|
||||
}
|
||||
|
||||
fn backpack_inputs(
|
||||
backpack: Single<&Backpack, With<LocalPlayer>>,
|
||||
mut state: ResMut<BackpackUiState>,
|
||||
mut backpack_inputs: MessageReader<crate::control::BackpackButtonPress>,
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for input in backpack_inputs.read() {
|
||||
match input {
|
||||
BackpackButtonPress::Toggle => {
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
}
|
||||
BackpackButtonPress::Swap => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
commands.client_trigger(BackpackSwapEvent(state.current_slot));
|
||||
}
|
||||
BackpackButtonPress::Left => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot > 0 {
|
||||
state.current_slot -= 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
BackpackButtonPress::Right => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot < state.count.saturating_sub(1) {
|
||||
state.current_slot += 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_on_change(
|
||||
backpack: Single<Ref<Backpack>, With<LocalPlayer>>,
|
||||
mut state: ResMut<BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if backpack.is_changed() || backpack.reloading() {
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_backpack_ui(backpack: &Backpack, state: &mut BackpackUiState, time: f32) {
|
||||
state.count = backpack.heads.len();
|
||||
|
||||
state.scroll = state
|
||||
.scroll
|
||||
.min(state.count.saturating_sub(BACKPACK_HEAD_SLOTS));
|
||||
|
||||
if state.current_slot >= state.scroll + BACKPACK_HEAD_SLOTS {
|
||||
state.scroll = state.current_slot.saturating_sub(BACKPACK_HEAD_SLOTS - 1);
|
||||
}
|
||||
if state.current_slot < state.scroll {
|
||||
state.scroll = state.current_slot;
|
||||
}
|
||||
|
||||
for i in 0..BACKPACK_HEAD_SLOTS {
|
||||
if let Some(head) = backpack.heads.get(i + state.scroll) {
|
||||
state.heads[i] = Some(UiHeadState::new(*head, time));
|
||||
} else {
|
||||
state.heads[i] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
pub mod backpack_ui;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(backpack_ui::plugin);
|
||||
}
|
||||
192
crates/hedz_reloaded/src/client/camera.rs
Normal file
192
crates/hedz_reloaded/src/client/camera.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
control::{Inputs, LookDirMovement, ViewMode},
|
||||
loading_assets::UIAssets,
|
||||
physics_layers::GameLayer,
|
||||
player::LocalPlayer,
|
||||
};
|
||||
use avian3d::prelude::{
|
||||
Collider, LayerMask, PhysicsLayer as _, ShapeCastConfig, SpatialQuery, SpatialQueryFilter,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Requested camera rotation based on various input sources (keyboard, gamepad)
|
||||
#[derive(Component, Reflect, Debug, Default, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
pub struct CameraRotationInput(pub Vec2);
|
||||
|
||||
#[derive(Resource, Reflect, Debug, Default)]
|
||||
#[reflect(Resource)]
|
||||
pub struct CameraState {
|
||||
pub cutscene: bool,
|
||||
pub view_mode: ViewMode,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Debug, Default)]
|
||||
struct CameraUi;
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct MainCamera {
|
||||
pub enabled: bool,
|
||||
dir: Dir3,
|
||||
distance: f32,
|
||||
target_offset: Vec3,
|
||||
}
|
||||
|
||||
impl MainCamera {
|
||||
fn new(arm: Vec3) -> Self {
|
||||
let (dir, distance) = Dir3::new_and_length(arm).expect("invalid arm length");
|
||||
Self {
|
||||
enabled: true,
|
||||
dir,
|
||||
distance,
|
||||
target_offset: Vec3::new(0., 2., 0.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<CameraRotationInput>();
|
||||
app.register_type::<CameraState>();
|
||||
app.register_type::<MainCamera>();
|
||||
|
||||
app.init_resource::<CameraState>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), startup);
|
||||
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
(update, update_ui, update_look_around, rotate_view).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
|
||||
fn startup(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
MainCamera::new(Vec3::new(0., 1.8, 15.)),
|
||||
CameraRotationInput::default(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update_look_around(
|
||||
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||
mut cam_state: ResMut<CameraState>,
|
||||
) {
|
||||
let view_mode = inputs.view_mode;
|
||||
|
||||
if view_mode != cam_state.view_mode {
|
||||
cam_state.view_mode = view_mode;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update_ui(
|
||||
mut commands: Commands,
|
||||
cam_state: Res<CameraState>,
|
||||
assets: Res<UIAssets>,
|
||||
query: Query<Entity, With<CameraUi>>,
|
||||
) {
|
||||
if cam_state.is_changed() {
|
||||
let show_free_cam_ui = cam_state.view_mode.is_free() || cam_state.cutscene;
|
||||
|
||||
if show_free_cam_ui {
|
||||
commands.spawn((
|
||||
CameraUi,
|
||||
Node {
|
||||
margin: UiRect::top(Val::Px(20.))
|
||||
.with_left(Val::Auto)
|
||||
.with_right(Val::Auto),
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
children![(
|
||||
Node {
|
||||
display: Display::Block,
|
||||
position_type: PositionType::Absolute,
|
||||
..default()
|
||||
},
|
||||
ImageNode::new(assets.camera.clone()),
|
||||
)],
|
||||
));
|
||||
} else {
|
||||
for entity in query.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
cam: Single<
|
||||
(&MainCamera, &mut Transform, &CameraRotationInput),
|
||||
(Without<CameraTarget>, Without<CameraArmRotation>),
|
||||
>,
|
||||
target_q: Single<
|
||||
(&Transform, &Children),
|
||||
(
|
||||
With<CameraTarget>,
|
||||
With<LocalPlayer>,
|
||||
Without<CameraArmRotation>,
|
||||
),
|
||||
>,
|
||||
arm_rotation: Query<&Transform, With<CameraArmRotation>>,
|
||||
spatial_query: SpatialQuery,
|
||||
cam_state: Res<CameraState>,
|
||||
) {
|
||||
if cam_state.cutscene {
|
||||
return;
|
||||
}
|
||||
|
||||
let (camera, mut cam_transform, cam_rotation_input) = cam.into_inner();
|
||||
|
||||
let (target_q, children) = target_q.into_inner();
|
||||
|
||||
let arm_tf = children
|
||||
.iter()
|
||||
.find_map(|child| arm_rotation.get(child).ok())
|
||||
.unwrap();
|
||||
|
||||
if !camera.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = target_q.translation + camera.target_offset;
|
||||
|
||||
let direction = arm_tf.rotation * Quat::from_rotation_y(cam_rotation_input.x) * camera.dir;
|
||||
|
||||
let max_distance = camera.distance;
|
||||
|
||||
let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits()));
|
||||
let cam_pos = if let Some(first_hit) = spatial_query.cast_shape(
|
||||
&Collider::sphere(0.5),
|
||||
target,
|
||||
Quat::IDENTITY,
|
||||
direction,
|
||||
&ShapeCastConfig::from_max_distance(max_distance),
|
||||
&filter,
|
||||
) {
|
||||
let distance = first_hit.distance;
|
||||
target + (direction * distance)
|
||||
} else {
|
||||
target + (direction * camera.distance)
|
||||
};
|
||||
|
||||
*cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn rotate_view(
|
||||
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||
look_dir: Res<LookDirMovement>,
|
||||
mut cam: Single<&mut CameraRotationInput>,
|
||||
) {
|
||||
if !inputs.view_mode.is_free() {
|
||||
cam.x = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
cam.0 += look_dir.0 * -0.001;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
control::{ControllerSet, Inputs, LookDirMovement},
|
||||
control::{ControllerSet, Inputs, LookDirMovement, SelectedController},
|
||||
player::{LocalPlayer, PlayerBodyMesh},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
@@ -19,14 +19,20 @@ pub fn plugin(app: &mut App) {
|
||||
fn rotate_rig(
|
||||
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||
look_dir: Res<LookDirMovement>,
|
||||
local_player: Single<&Children, With<LocalPlayer>>,
|
||||
local_player: Single<(&Children, &SelectedController), With<LocalPlayer>>,
|
||||
mut player_mesh: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||
) {
|
||||
if inputs.view_mode {
|
||||
if inputs.view_mode.is_free() {
|
||||
return;
|
||||
}
|
||||
|
||||
local_player.iter().find(|&child| {
|
||||
let (local_player_children, selected_controller) = *local_player;
|
||||
|
||||
if !matches!(selected_controller, SelectedController::Flying) {
|
||||
return;
|
||||
}
|
||||
|
||||
local_player_children.iter().find(|&child| {
|
||||
if let Ok(mut rig_transform) = player_mesh.get_mut(child) {
|
||||
let look_dir = look_dir.0;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
client::control::CharacterInputEnabled,
|
||||
control::{
|
||||
BackpackButtonPress, CashHealPressed, ClientInputs, ControllerSet, Inputs, LocalInputs,
|
||||
LookDirMovement, SelectLeftPressed, SelectRightPressed,
|
||||
LookDirMovement, SelectLeftPressed, SelectRightPressed, ViewMode,
|
||||
},
|
||||
player::{LocalPlayer, PlayerBodyMesh},
|
||||
};
|
||||
@@ -85,9 +85,14 @@ fn reset_control_state_on_disable(
|
||||
|
||||
fn get_lookdir(
|
||||
mut inputs: Single<&mut LocalInputs>,
|
||||
rig_transform: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||
player: Single<&Children, With<LocalPlayer>>,
|
||||
rig_transform: Query<&GlobalTransform, With<PlayerBodyMesh>>,
|
||||
) {
|
||||
inputs.0.look_dir = if let Some(ref rig_transform) = rig_transform {
|
||||
let rig_transform = player
|
||||
.iter()
|
||||
.find_map(|child| rig_transform.get(child).ok());
|
||||
|
||||
inputs.0.look_dir = if let Some(rig_transform) = rig_transform {
|
||||
rig_transform.forward().as_vec3()
|
||||
} else {
|
||||
Vec3::NEG_Z
|
||||
@@ -141,8 +146,11 @@ fn gamepad_controls(
|
||||
|
||||
inputs.0.move_dir += move_dir.clamp_length_max(1.0);
|
||||
inputs.0.jump |= gamepad.pressed(GamepadButton::South);
|
||||
inputs.0.view_mode |= gamepad.pressed(GamepadButton::LeftTrigger2);
|
||||
inputs.0.trigger |= gamepad.pressed(GamepadButton::RightTrigger2);
|
||||
inputs
|
||||
.0
|
||||
.view_mode
|
||||
.merge_input(gamepad.pressed(GamepadButton::LeftTrigger2));
|
||||
|
||||
if gamepad.just_pressed(GamepadButton::DPadUp) {
|
||||
backpack_inputs.write(BackpackButtonPress::Toggle);
|
||||
@@ -207,8 +215,8 @@ fn keyboard_controls(
|
||||
|
||||
inputs.0.move_dir = direction;
|
||||
inputs.0.jump = keyboard.pressed(KeyCode::Space);
|
||||
inputs.0.view_mode = keyboard.pressed(KeyCode::Tab);
|
||||
inputs.0.trigger = mouse.pressed(MouseButton::Left);
|
||||
inputs.0.view_mode = ViewMode::from_input(keyboard.pressed(KeyCode::Tab));
|
||||
|
||||
if keyboard.just_pressed(KeyCode::KeyB) {
|
||||
backpack_inputs.write(BackpackButtonPress::Toggle);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{GameState, control::ControllerSet};
|
||||
use crate::{
|
||||
GameState,
|
||||
control::{ControllerSet, Inputs},
|
||||
player::{LocalPlayer, Player, PlayerBodyMesh},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::client::ClientSystems;
|
||||
|
||||
@@ -22,4 +26,22 @@ pub fn plugin(app: &mut App) {
|
||||
.before(ClientSystems::Receive)
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
rotate_others.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
|
||||
fn rotate_others(
|
||||
players: Query<(&Inputs, &Children), (With<Player>, Without<LocalPlayer>)>,
|
||||
mut rig: Query<(&mut Transform, &PlayerBodyMesh)>,
|
||||
) {
|
||||
for (input, children) in players.iter() {
|
||||
for child in children.iter() {
|
||||
if let Ok((mut rig, _)) = rig.get_mut(child) {
|
||||
*rig = rig.looking_to(input.look_dir, Vec3::Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
crates/hedz_reloaded/src/client/cutscene.rs
Normal file
104
crates/hedz_reloaded/src/client/cutscene.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
client::camera::{CameraState, MainCamera},
|
||||
cutscene::StartCutscene,
|
||||
global_observer,
|
||||
tb_entities::{CameraTarget, CutsceneCamera, CutsceneCameraMovementEnd},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_trenchbroom::prelude::*;
|
||||
|
||||
#[derive(Resource, Debug, Default)]
|
||||
enum CutsceneState {
|
||||
#[default]
|
||||
None,
|
||||
Playing {
|
||||
timer: Timer,
|
||||
camera_start: Transform,
|
||||
camera_end: Transform,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<CutsceneState>();
|
||||
app.add_systems(Update, update.run_if(in_state(GameState::Playing)));
|
||||
|
||||
global_observer!(app, on_start_cutscene);
|
||||
}
|
||||
|
||||
fn on_start_cutscene(
|
||||
trigger: On<StartCutscene>,
|
||||
mut cam_state: ResMut<CameraState>,
|
||||
mut cutscene_state: ResMut<CutsceneState>,
|
||||
cutscenes: Query<(&Transform, &CutsceneCamera, &Target), Without<MainCamera>>,
|
||||
cutscene_movement: Query<
|
||||
(&Transform, &CutsceneCameraMovementEnd, &Target),
|
||||
Without<MainCamera>,
|
||||
>,
|
||||
cam_target: Query<(&Transform, &CameraTarget), Without<MainCamera>>,
|
||||
) {
|
||||
let cutscene = trigger.event().0.clone();
|
||||
|
||||
cam_state.cutscene = true;
|
||||
|
||||
// asumes `name` and `targetname` are equal
|
||||
let Some((t, _, target)) = cutscenes
|
||||
.iter()
|
||||
.find(|(_, cutscene_camera, _)| cutscene == cutscene_camera.name)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let move_end = cutscene_movement
|
||||
.iter()
|
||||
.find(|(_, _, target)| cutscene == target.target.clone().unwrap_or_default())
|
||||
.map(|(t, _, _)| *t)
|
||||
.unwrap_or_else(|| *t);
|
||||
|
||||
let Some((target, _)) = cam_target.iter().find(|(_, camera_target)| {
|
||||
camera_target.targetname == target.target.clone().unwrap_or_default()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
*cutscene_state = CutsceneState::Playing {
|
||||
timer: Timer::from_seconds(2.0, TimerMode::Once),
|
||||
camera_start: t.looking_at(target.translation, Vec3::Y),
|
||||
camera_end: move_end.looking_at(target.translation, Vec3::Y),
|
||||
};
|
||||
}
|
||||
|
||||
fn update(
|
||||
mut cam_state: ResMut<CameraState>,
|
||||
mut cutscene_state: ResMut<CutsceneState>,
|
||||
mut cam: Query<&mut Transform, With<MainCamera>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if let CutsceneState::Playing {
|
||||
timer,
|
||||
camera_start,
|
||||
camera_end,
|
||||
} = &mut *cutscene_state
|
||||
{
|
||||
cam_state.cutscene = true;
|
||||
timer.tick(time.delta());
|
||||
|
||||
let t = Transform::from_translation(
|
||||
camera_start
|
||||
.translation
|
||||
.lerp(camera_end.translation, timer.fraction()),
|
||||
)
|
||||
.with_rotation(
|
||||
camera_start
|
||||
.rotation
|
||||
.lerp(camera_end.rotation, timer.fraction()),
|
||||
);
|
||||
|
||||
let _ = cam.single_mut().map(|mut cam| *cam = t);
|
||||
|
||||
if timer.is_finished() {
|
||||
cam_state.cutscene = false;
|
||||
*cutscene_state = CutsceneState::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
GameState,
|
||||
abilities::Healing,
|
||||
loading_assets::{AudioAssets, GameAssets},
|
||||
utils::{billboards::Billboard, observers::global_observer},
|
||||
utils::{Billboard, observers::global_observer},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use rand::{Rng, thread_rng};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
config::NetworkingConfig,
|
||||
config::NetConfig,
|
||||
protocol::{
|
||||
ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, messages::DespawnTbMapEntity,
|
||||
},
|
||||
@@ -17,18 +17,17 @@ use bevy_replicon::{
|
||||
};
|
||||
use bevy_replicon_renet::{
|
||||
RenetChannelsExt,
|
||||
netcode::{ClientAuthentication, NetcodeClientTransport, NetcodeError},
|
||||
renet::{ConnectionConfig, RenetClient},
|
||||
};
|
||||
use bevy_steamworks::Client;
|
||||
use bevy_trenchbroom::geometry::Brushes;
|
||||
use std::{
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub mod aim;
|
||||
pub mod audio;
|
||||
pub mod backpack;
|
||||
mod backpack;
|
||||
pub mod camera;
|
||||
pub mod control;
|
||||
pub mod cutscene;
|
||||
pub mod debug;
|
||||
pub mod enemy;
|
||||
pub mod heal_effect;
|
||||
@@ -37,25 +36,30 @@ mod settings;
|
||||
pub mod setup;
|
||||
pub mod steam;
|
||||
pub mod ui;
|
||||
mod utils;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins((
|
||||
backpack::plugin,
|
||||
aim::plugin,
|
||||
audio::plugin,
|
||||
control::plugin,
|
||||
debug::plugin,
|
||||
enemy::plugin,
|
||||
heal_effect::plugin,
|
||||
player::plugin,
|
||||
setup::plugin,
|
||||
audio::plugin,
|
||||
steam::plugin,
|
||||
ui::plugin,
|
||||
settings::plugin,
|
||||
backpack::plugin,
|
||||
camera::plugin,
|
||||
utils::billboards::plugin,
|
||||
cutscene::plugin,
|
||||
));
|
||||
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Connecting),
|
||||
connect_to_server.run_if(|config: Res<NetworkingConfig>| config.server.is_some()),
|
||||
connect_to_server.run_if(|config: Res<NetConfig>| config.is_client()),
|
||||
);
|
||||
app.add_systems(Update, despawn_absent_map_entities);
|
||||
app.add_systems(
|
||||
@@ -89,8 +93,9 @@ fn on_disconnect() {
|
||||
|
||||
fn connect_to_server(
|
||||
mut commands: Commands,
|
||||
config: Res<NetworkingConfig>,
|
||||
config: Res<NetConfig>,
|
||||
channels: Res<RepliconChannels>,
|
||||
steam_client: Option<Res<Client>>,
|
||||
) -> Result {
|
||||
let server_channels_config = channels.server_configs();
|
||||
let client_channels_config = channels.client_configs();
|
||||
@@ -102,32 +107,47 @@ fn connect_to_server(
|
||||
});
|
||||
|
||||
commands.insert_resource(client);
|
||||
commands.insert_resource(client_transport(&config)?);
|
||||
|
||||
if let NetConfig::SteamClient(host_steam_id) = &*config {
|
||||
let Some(steam_client) = steam_client else {
|
||||
return Err("Steam client not found".into());
|
||||
};
|
||||
|
||||
info!("connecting to steam host: {host_steam_id:?}");
|
||||
let transport = bevy_replicon_renet::steam::SteamClientTransport::new(
|
||||
(**steam_client).clone(),
|
||||
host_steam_id,
|
||||
)?;
|
||||
|
||||
commands.insert_resource(transport);
|
||||
} else if let NetConfig::NetcodeClient(host_addr) = &*config {
|
||||
use std::time::SystemTime;
|
||||
|
||||
info!("connecting to netcode host: {host_addr:?}");
|
||||
|
||||
let current_time = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
let client_id = current_time.as_millis() as u64;
|
||||
let socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))?;
|
||||
let authentication = bevy_replicon_renet::netcode::ClientAuthentication::Unsecure {
|
||||
client_id,
|
||||
protocol_id: 0,
|
||||
server_addr: *host_addr,
|
||||
user_data: None,
|
||||
};
|
||||
let transport = bevy_replicon_renet::netcode::NetcodeClientTransport::new(
|
||||
current_time,
|
||||
authentication,
|
||||
socket,
|
||||
)?;
|
||||
|
||||
commands.insert_resource(transport);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn client_transport(config: &NetworkingConfig) -> Result<NetcodeClientTransport, NetcodeError> {
|
||||
let current_time = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap();
|
||||
let client_id = current_time.as_millis() as u64;
|
||||
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
|
||||
let server_addr = config
|
||||
.server
|
||||
.flatten()
|
||||
.unwrap_or_else(|| "127.0.0.1:31111".parse().unwrap());
|
||||
let authentication = ClientAuthentication::Unsecure {
|
||||
client_id,
|
||||
protocol_id: 0,
|
||||
server_addr,
|
||||
user_data: None,
|
||||
};
|
||||
|
||||
info!("attempting connection to {server_addr}");
|
||||
NetcodeClientTransport::new(current_time, authentication, socket)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn migrate_remote_entities(
|
||||
query: Query<(Entity, &TbMapEntityId), (Added<TbMapEntityId>, With<ConfirmHistory>)>,
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
global_observer,
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
loading_assets::AudioAssets,
|
||||
player::{LocalPlayer, PlayerBodyMesh},
|
||||
player::{LocalPlayer, Player, PlayerBodyMesh},
|
||||
protocol::{ClientHeadChanged, PlaySound, PlayerId, messages::AssignClientPlayer},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
@@ -59,23 +59,38 @@ pub enum PlayerAssignmentState {
|
||||
Confirmed,
|
||||
}
|
||||
|
||||
// TODO: currently a networked message.
|
||||
// can be done by just using local change detection on `ActiveHead`?
|
||||
fn on_client_update_head_mesh(
|
||||
trigger: On<ClientHeadChanged>,
|
||||
mut commands: Commands,
|
||||
body_mesh: Single<(Entity, &Children), With<PlayerBodyMesh>>,
|
||||
player: Query<(&Children, &PlayerId), With<Player>>,
|
||||
body_mesh: Query<(Entity, &Children), With<PlayerBodyMesh>>,
|
||||
head_db: Res<HeadsDatabase>,
|
||||
audio_assets: Res<AudioAssets>,
|
||||
sfx: Query<&AudioPlayer>,
|
||||
) -> Result {
|
||||
let head = trigger.0 as usize;
|
||||
let (body_mesh, mesh_children) = *body_mesh;
|
||||
let (player_children, _) = player
|
||||
.iter()
|
||||
.find(|(_, player_id)| **player_id == trigger.player)
|
||||
.unwrap();
|
||||
|
||||
let (body_mesh, body_mesh_children) = player_children
|
||||
.iter()
|
||||
.find_map(|child| body_mesh.get(child).ok())
|
||||
.unwrap();
|
||||
|
||||
let head = trigger.head;
|
||||
|
||||
let head_str = head_db.head_key(head);
|
||||
|
||||
commands.trigger(PlaySound::Head(head_str.to_string()));
|
||||
|
||||
//TODO: make part of full character mesh later
|
||||
for child in mesh_children.iter().filter(|child| sfx.contains(*child)) {
|
||||
for child in body_mesh_children
|
||||
.iter()
|
||||
.filter(|child| sfx.contains(*child))
|
||||
{
|
||||
commands.entity(child).despawn();
|
||||
}
|
||||
if head_db.head_stats(head).controls == HeadControls::Plane {
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_pkv::prelude::*;
|
||||
|
||||
use crate::{client::audio::SoundSettings, utils::Debounce};
|
||||
use bevy::prelude::*;
|
||||
use bevy_persistent::{Persistent, StorageFormat};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.insert_resource(PkvStore::new("Rustunit", "HEDZ"));
|
||||
app.insert_resource(
|
||||
Persistent::<SoundSettings>::builder()
|
||||
.name("audio")
|
||||
.format(StorageFormat::Ron)
|
||||
.path(
|
||||
dirs::config_dir()
|
||||
.unwrap()
|
||||
.join("com.rustunit.hedzreloaded")
|
||||
.join("audio.ron"),
|
||||
)
|
||||
.default(SoundSettings::default())
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
app.add_systems(Update, persist_settings);
|
||||
app.add_systems(Startup, load_settings);
|
||||
@@ -12,7 +24,7 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
fn persist_settings(
|
||||
settings: Res<SoundSettings>,
|
||||
mut pkv: ResMut<PkvStore>,
|
||||
mut persistent: ResMut<Persistent<SoundSettings>>,
|
||||
mut debounce: Debounce<1000>,
|
||||
) -> Result {
|
||||
if settings.is_changed() {
|
||||
@@ -20,16 +32,12 @@ fn persist_settings(
|
||||
}
|
||||
|
||||
if debounce.finished() {
|
||||
pkv.set("audio", &*settings)?;
|
||||
persistent.set(*settings)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_settings(mut settings: ResMut<SoundSettings>, pkv: Res<PkvStore>) -> Result {
|
||||
if let Ok(loaded) = pkv.get::<SoundSettings>("audio") {
|
||||
*settings = loaded;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn load_settings(persistent: Res<Persistent<SoundSettings>>, mut settings: ResMut<SoundSettings>) {
|
||||
*settings = *persistent.get();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{DebugVisuals, camera::MainCamera};
|
||||
use crate::{DebugVisuals, client::camera::MainCamera};
|
||||
use bevy::{core_pipeline::tonemapping::Tonemapping, prelude::*, render::view::ColorGrading};
|
||||
use bevy_trenchbroom::TrenchBroomServer;
|
||||
|
||||
|
||||
@@ -61,6 +61,14 @@ fn test_steam_system(steam_client: Res<Client>) {
|
||||
},
|
||||
);
|
||||
|
||||
let id = steam_client.user().steam_id();
|
||||
|
||||
info!("Steam ID: {:?}", id);
|
||||
|
||||
steam_client
|
||||
.friends()
|
||||
.set_rich_presence("connect", Some(id.raw().to_string().as_str()));
|
||||
|
||||
for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) {
|
||||
info!(
|
||||
"Steam Friend: {:?} - {}({:?})",
|
||||
|
||||
@@ -1,15 +1,47 @@
|
||||
use crate::{
|
||||
GameState, HEDZ_GREEN,
|
||||
backpack::backpack_ui::{
|
||||
BACKPACK_HEAD_SLOTS, BackpackCountText, BackpackMarker, BackpackUiState, HeadDamage,
|
||||
HeadImage, HeadSelector,
|
||||
},
|
||||
heads::HeadsImages,
|
||||
client::ui::heads_ui::{HeadsImages, UiHeadState},
|
||||
loading_assets::UIAssets,
|
||||
};
|
||||
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
||||
|
||||
pub static BACKPACK_HEAD_SLOTS: usize = 5;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackMarker;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackCountText;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadSelector(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadImage(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadDamage(pub usize);
|
||||
|
||||
#[derive(Resource, Default, Debug, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct BackpackUiState {
|
||||
pub heads: [Option<UiHeadState>; 5],
|
||||
pub scroll: usize,
|
||||
pub count: usize,
|
||||
pub current_slot: usize,
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl BackpackUiState {
|
||||
pub fn relative_current_slot(&self) -> usize {
|
||||
self.current_slot.saturating_sub(self.scroll)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<BackpackUiState>();
|
||||
app.init_resource::<BackpackUiState>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
@@ -152,10 +184,14 @@ fn spawn_head_ui(
|
||||
}
|
||||
|
||||
fn update_visibility(
|
||||
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||
state: Res<BackpackUiState>,
|
||||
mut backpack: Single<&mut Visibility, (With<BackpackMarker>, Without<BackpackCountText>)>,
|
||||
mut count: Single<&mut Visibility, (Without<BackpackMarker>, With<BackpackCountText>)>,
|
||||
) {
|
||||
if !state.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
**backpack = if state.open {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
@@ -170,10 +206,14 @@ fn update_visibility(
|
||||
}
|
||||
|
||||
fn update_count(
|
||||
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||
state: Res<BackpackUiState>,
|
||||
text: Option<Single<Entity, With<BackpackCountText>>>,
|
||||
mut writer: TextUiWriter,
|
||||
) {
|
||||
if !state.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(text) = text else {
|
||||
return;
|
||||
};
|
||||
@@ -182,12 +222,16 @@ fn update_count(
|
||||
}
|
||||
|
||||
fn update(
|
||||
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||
state: Res<BackpackUiState>,
|
||||
heads_images: Res<HeadsImages>,
|
||||
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
||||
mut head_damage: Query<(&HeadDamage, &mut Node), Without<HeadSelector>>,
|
||||
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
||||
) {
|
||||
if !state.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
||||
if let Some(head) = &state.heads[*head] {
|
||||
*vis = Visibility::Inherited;
|
||||
47
crates/hedz_reloaded/src/client/ui/cash_ui.rs
Normal file
47
crates/hedz_reloaded/src/client/ui/cash_ui.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::{
|
||||
GameState, HEDZ_GREEN, cash::CashInventory, loading_assets::UIAssets, player::LocalPlayer,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[reflect(Component)]
|
||||
struct CashText;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(Update, update_ui.run_if(in_state(GameState::Playing)));
|
||||
}
|
||||
|
||||
fn update_ui(
|
||||
cash: Single<&CashInventory, (Changed<CashInventory>, With<LocalPlayer>)>,
|
||||
text: Query<Entity, With<CashText>>,
|
||||
mut writer: TextUiWriter,
|
||||
) {
|
||||
let Some(text) = text.iter().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
*writer.text(text, 0) = cash.cash.to_string();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||
commands.spawn((
|
||||
Name::new("cash-ui"),
|
||||
Text::new("0"),
|
||||
TextShadow::default(),
|
||||
CashText,
|
||||
TextFont {
|
||||
font: assets.font.clone(),
|
||||
font_size: 34.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(HEDZ_GREEN.into()),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
bottom: Val::Px(40.0),
|
||||
left: Val::Px(100.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
use super::{ActiveHeads, HEAD_SLOTS};
|
||||
#[cfg(feature = "client")]
|
||||
use crate::heads::HeadsImages;
|
||||
use crate::{
|
||||
GameState, backpack::UiHeadState, loading_assets::UIAssets, player::Player, protocol::is_server,
|
||||
GameState,
|
||||
heads::{ActiveHeads, HEAD_COUNT, HEAD_SLOTS, HeadState},
|
||||
heads_database::HeadsDatabase,
|
||||
loading_assets::UIAssets,
|
||||
player::LocalPlayer,
|
||||
};
|
||||
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct HeadsImages {
|
||||
pub heads: Vec<Handle<Image>>,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[reflect(Component)]
|
||||
struct HeadSelector(pub usize);
|
||||
@@ -20,29 +26,78 @@ struct HeadImage(pub usize);
|
||||
#[reflect(Component)]
|
||||
struct HeadDamage(pub usize);
|
||||
|
||||
#[derive(Component, Default, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub struct UiActiveHeads {
|
||||
#[derive(Resource, Default, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Resource)]
|
||||
struct UiActiveHeads {
|
||||
heads: [Option<UiHeadState>; 5],
|
||||
selected_slot: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Default, Serialize, Deserialize)]
|
||||
pub struct UiHeadState {
|
||||
pub head: usize,
|
||||
pub health: f32,
|
||||
pub ammo: f32,
|
||||
pub reloading: Option<f32>,
|
||||
}
|
||||
|
||||
impl UiHeadState {
|
||||
pub fn damage(&self) -> f32 {
|
||||
1. - self.health
|
||||
}
|
||||
|
||||
pub fn ammo_used(&self) -> f32 {
|
||||
1. - self.ammo
|
||||
}
|
||||
|
||||
pub fn reloading(&self) -> Option<f32> {
|
||||
self.reloading
|
||||
}
|
||||
|
||||
pub fn new(value: HeadState, time: f32) -> Self {
|
||||
let reloading = if value.has_ammo() {
|
||||
None
|
||||
} else {
|
||||
Some((time - value.last_use) / value.reload_duration)
|
||||
};
|
||||
|
||||
Self {
|
||||
head: value.head,
|
||||
ammo: value.ammo as f32 / value.ammo_max as f32,
|
||||
health: value.health as f32 / value.health_max as f32,
|
||||
reloading,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<HeadDamage>();
|
||||
app.register_type::<UiActiveHeads>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
sync.run_if(in_state(GameState::Playing).and(is_server)),
|
||||
);
|
||||
#[cfg(feature = "client")]
|
||||
app.init_resource::<UiActiveHeads>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), (setup, setup_heads_images));
|
||||
app.add_systems(FixedUpdate, sync.run_if(in_state(GameState::Playing)));
|
||||
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(update, update_ammo, update_health).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_heads_images(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
heads: Res<HeadsDatabase>,
|
||||
) {
|
||||
// TODO: load via asset loader
|
||||
let heads = (0usize..HEAD_COUNT)
|
||||
.map(|i| asset_server.load(format!("ui/heads/{}.png", heads.head_key(i))))
|
||||
.collect();
|
||||
|
||||
commands.insert_resource(HeadsImages { heads });
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||
commands.spawn((
|
||||
Name::new("heads-ui"),
|
||||
@@ -176,7 +231,7 @@ fn spawn_head_ui(
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update(
|
||||
res: Single<&UiActiveHeads>,
|
||||
res: Res<UiActiveHeads>,
|
||||
heads_images: Res<HeadsImages>,
|
||||
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
||||
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
||||
@@ -200,10 +255,14 @@ fn update(
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update_ammo(
|
||||
res: Single<&UiActiveHeads, Changed<UiActiveHeads>>,
|
||||
res: Res<UiActiveHeads>,
|
||||
heads: Query<&HeadImage>,
|
||||
mut gradients: Query<(&mut BackgroundGradient, &ChildOf)>,
|
||||
) {
|
||||
if !res.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (mut gradient, child_of) in gradients.iter_mut() {
|
||||
let Ok(HeadImage(head)) = heads.get(child_of.parent()) else {
|
||||
continue;
|
||||
@@ -229,26 +288,22 @@ fn update_ammo(
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn update_health(
|
||||
res: Single<&UiActiveHeads, Changed<UiActiveHeads>>,
|
||||
mut query: Query<(&mut Node, &HeadDamage)>,
|
||||
) {
|
||||
for (mut node, HeadDamage(head)) in query.iter_mut() {
|
||||
node.height = Val::Percent(res.heads[*head].map(|head| head.damage()).unwrap_or(0.) * 100.);
|
||||
fn update_health(res: Res<UiActiveHeads>, mut query: Query<(&mut Node, &HeadDamage)>) {
|
||||
if res.is_changed() {
|
||||
for (mut node, HeadDamage(head)) in query.iter_mut() {
|
||||
node.height =
|
||||
Val::Percent(res.heads[*head].map(|head| head.damage()).unwrap_or(0.) * 100.);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(
|
||||
active_heads: Query<Ref<ActiveHeads>, With<Player>>,
|
||||
mut state: Single<&mut UiActiveHeads>,
|
||||
active_heads: Single<Ref<ActiveHeads>, With<LocalPlayer>>,
|
||||
mut state: ResMut<UiActiveHeads>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let Ok(active_heads) = active_heads.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if active_heads.is_changed() || active_heads.reloading() {
|
||||
state.selected_slot = active_heads.selected_slot;
|
||||
state.selected_slot = active_heads.slot();
|
||||
|
||||
for i in 0..HEAD_SLOTS {
|
||||
state.heads[i] = active_heads
|
||||
@@ -1,7 +1,15 @@
|
||||
mod backpack_ui;
|
||||
mod cash_ui;
|
||||
mod heads_ui;
|
||||
mod pause;
|
||||
|
||||
pub use backpack_ui::{BACKPACK_HEAD_SLOTS, BackpackUiState};
|
||||
use bevy::prelude::*;
|
||||
pub use heads_ui::{HeadsImages, UiHeadState};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(heads_ui::plugin);
|
||||
app.add_plugins(backpack_ui::plugin);
|
||||
app.add_plugins(pause::plugin);
|
||||
app.add_plugins(cash_ui::plugin);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
use crate::camera::MainCamera;
|
||||
use crate::{client::camera::MainCamera, utils::Billboard};
|
||||
use bevy::prelude::*;
|
||||
use bevy_sprite3d::Sprite3dPlugin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Reflect, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[reflect(Component)]
|
||||
pub enum Billboard {
|
||||
#[default]
|
||||
All,
|
||||
XZ,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
if !app.is_plugin_added::<Sprite3dPlugin>() {
|
||||
app.add_plugins(Sprite3dPlugin);
|
||||
}
|
||||
|
||||
app.register_type::<Billboard>();
|
||||
app.add_systems(Update, (face_camera, face_camera_no_parent));
|
||||
}
|
||||
|
||||
1
crates/hedz_reloaded/src/client/utils/mod.rs
Normal file
1
crates/hedz_reloaded/src/client/utils/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod billboards;
|
||||
@@ -1,25 +1,90 @@
|
||||
use bevy::prelude::*;
|
||||
use clap::Parser;
|
||||
use std::net::SocketAddr;
|
||||
use steamworks::SteamId;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
let config = NetworkingConfig::parse();
|
||||
|
||||
let config: NetConfig = config.into();
|
||||
|
||||
info!("net config: {:?}", config);
|
||||
|
||||
app.insert_resource(config);
|
||||
}
|
||||
|
||||
#[derive(Resource, Parser, Debug)]
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct NetworkingConfig {
|
||||
/// The IP/port to connect to.
|
||||
/// If `None`, host a local server.
|
||||
/// If Some(None), connect to the default server (`127.0.0.1:31111`)
|
||||
/// Otherwise, connect to the given server.
|
||||
/// Does nothing on the server.
|
||||
struct NetworkingConfig {
|
||||
/// Steam id of the host to connect to
|
||||
#[arg(long)]
|
||||
pub server: Option<Option<SocketAddr>>,
|
||||
/// Whether or not to open a port when opening the client, for other clients
|
||||
/// to connect. Does nothing if `server` is set.
|
||||
pub steam_host_id: Option<String>,
|
||||
|
||||
/// Act as steam host
|
||||
#[arg(long)]
|
||||
pub host: bool,
|
||||
pub steam_host: bool,
|
||||
|
||||
/// Act as host using netcode, so we have to define our port
|
||||
#[arg(long)]
|
||||
pub netcode_host: Option<Option<u16>>,
|
||||
|
||||
/// Host address we connect to as a client
|
||||
#[arg(long)]
|
||||
pub netcode_client: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug)]
|
||||
pub enum NetConfig {
|
||||
Singleplayer,
|
||||
SteamHost,
|
||||
NetcodeHost { port: u16 },
|
||||
SteamClient(SteamId),
|
||||
NetcodeClient(SocketAddr),
|
||||
}
|
||||
|
||||
impl NetConfig {
|
||||
pub fn is_client(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
NetConfig::SteamClient(_) | NetConfig::NetcodeClient(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_host(&self) -> bool {
|
||||
matches!(self, NetConfig::SteamHost | NetConfig::NetcodeHost { .. })
|
||||
}
|
||||
|
||||
pub fn is_singleplayer(&self) -> bool {
|
||||
!self.is_client() && !self.is_host()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetworkingConfig> for NetConfig {
|
||||
fn from(config: NetworkingConfig) -> Self {
|
||||
match (
|
||||
config.steam_host,
|
||||
config.steam_host_id,
|
||||
config.netcode_host,
|
||||
config.netcode_client,
|
||||
) {
|
||||
(false, None, None, None) => Self::Singleplayer,
|
||||
(true, None, None, None) => Self::SteamHost,
|
||||
(false, Some(id), None, None) => Self::SteamClient(parse_steam_id(id)),
|
||||
(false, None, Some(port), None) => Self::NetcodeHost {
|
||||
port: port.unwrap_or(31111),
|
||||
},
|
||||
(false, None, None, Some(addr)) => Self::NetcodeClient(parse_addr(addr)),
|
||||
_ => panic!("Invalid configuration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_addr(addr: Option<String>) -> SocketAddr {
|
||||
addr.and_then(|addr| addr.parse().ok())
|
||||
.unwrap_or_else(|| "127.0.0.1:31111".parse().unwrap())
|
||||
}
|
||||
|
||||
fn parse_steam_id(id: String) -> SteamId {
|
||||
let id: u64 = id.parse().unwrap();
|
||||
SteamId::from_raw(id)
|
||||
}
|
||||
|
||||
@@ -72,12 +72,22 @@ fn set_animation_flags(
|
||||
pub fn reset_upon_switch(
|
||||
mut c: Commands,
|
||||
mut event_controller_switch: MessageReader<ControllerSwitchEvent>,
|
||||
selected_controller: Res<SelectedController>,
|
||||
mut rig_transforms: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||
mut controllers: Query<(&mut KinematicVelocity, &Children, &Inputs), With<Player>>,
|
||||
mut controllers: Query<
|
||||
(
|
||||
&mut KinematicVelocity,
|
||||
&Children,
|
||||
&Inputs,
|
||||
&SelectedController,
|
||||
),
|
||||
With<Player>,
|
||||
>,
|
||||
) {
|
||||
for &ControllerSwitchEvent { controller } in event_controller_switch.read() {
|
||||
let (mut velocity, children, inputs) = controllers.get_mut(controller).unwrap();
|
||||
let (mut velocity, children, inputs, selected_controller) =
|
||||
controllers.get_mut(controller).unwrap();
|
||||
|
||||
info!("resetting controller");
|
||||
|
||||
velocity.0 = Vec3::ZERO;
|
||||
|
||||
@@ -165,6 +175,7 @@ impl Default for MovementSpeedFactor {
|
||||
MoveInput,
|
||||
MovementSpeedFactor,
|
||||
TransformInterpolation,
|
||||
SelectedController::Running,
|
||||
CharacterMovement = RUNNING_MOVEMENT_CONFIG.movement,
|
||||
ControllerSettings = RUNNING_MOVEMENT_CONFIG.settings,
|
||||
CharacterGravity = RUNNING_MOVEMENT_CONFIG.gravity,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::ControllerSet;
|
||||
use crate::{
|
||||
GameState,
|
||||
control::{Inputs, controller_common::MovementSpeedFactor},
|
||||
control::{Inputs, SelectedController, controller_common::MovementSpeedFactor},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use happy_feet::prelude::MoveInput;
|
||||
@@ -19,8 +19,17 @@ impl Plugin for CharacterControllerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_controls(character: Single<(&mut MoveInput, &MovementSpeedFactor, &Inputs)>) {
|
||||
let (mut char_input, factor, inputs) = character.into_inner();
|
||||
|
||||
char_input.set(inputs.look_dir * factor.0);
|
||||
pub fn apply_controls(
|
||||
mut query: Query<(
|
||||
&mut MoveInput,
|
||||
&MovementSpeedFactor,
|
||||
&Inputs,
|
||||
&SelectedController,
|
||||
)>,
|
||||
) {
|
||||
for (mut move_input, factor, inputs, selected_controller) in query.iter_mut() {
|
||||
if *selected_controller == SelectedController::Flying {
|
||||
move_input.set(inputs.look_dir * factor.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
animation::AnimationFlags,
|
||||
control::{ControllerSet, ControllerSettings, Inputs, controller_common::MovementSpeedFactor},
|
||||
control::{
|
||||
ControllerSet, ControllerSettings, Inputs, SelectedController,
|
||||
controller_common::MovementSpeedFactor,
|
||||
},
|
||||
protocol::is_server,
|
||||
};
|
||||
#[cfg(feature = "client")]
|
||||
@@ -41,7 +44,7 @@ fn rotate_view(
|
||||
) {
|
||||
let (inputs, children) = controller.into_inner();
|
||||
|
||||
if inputs.view_mode {
|
||||
if inputs.view_mode.is_free() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +59,7 @@ fn rotate_view(
|
||||
}
|
||||
|
||||
fn apply_controls(
|
||||
character: Single<(
|
||||
mut query: Query<(
|
||||
&mut MoveInput,
|
||||
&mut Grounding,
|
||||
&mut KinematicVelocity,
|
||||
@@ -64,25 +67,39 @@ fn apply_controls(
|
||||
&ControllerSettings,
|
||||
&MovementSpeedFactor,
|
||||
&Inputs,
|
||||
&SelectedController,
|
||||
)>,
|
||||
) {
|
||||
let (mut move_input, mut grounding, mut velocity, mut flags, settings, move_factor, inputs) =
|
||||
character.into_inner();
|
||||
for (
|
||||
mut move_input,
|
||||
mut grounding,
|
||||
mut velocity,
|
||||
mut flags,
|
||||
settings,
|
||||
move_factor,
|
||||
inputs,
|
||||
selected_controller,
|
||||
) in query.iter_mut()
|
||||
{
|
||||
if *selected_controller != SelectedController::Running {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ground_normal = *grounding.normal().unwrap_or(Dir3::Y);
|
||||
let ground_normal = *grounding.normal().unwrap_or(Dir3::Y);
|
||||
|
||||
let mut direction = inputs.move_dir.extend(0.0).xzy();
|
||||
let look_dir_right = inputs.look_dir.cross(Vec3::Y);
|
||||
direction = (inputs.look_dir * direction.z) + (look_dir_right * direction.x);
|
||||
let y_projection = direction.project_onto(ground_normal);
|
||||
direction -= y_projection;
|
||||
direction = direction.normalize_or_zero();
|
||||
let mut direction = inputs.move_dir.extend(0.0).xzy();
|
||||
let look_dir_right = inputs.look_dir.cross(Vec3::Y);
|
||||
direction = (inputs.look_dir * direction.z) + (look_dir_right * direction.x);
|
||||
let y_projection = direction.project_onto(ground_normal);
|
||||
direction -= y_projection;
|
||||
direction = direction.normalize_or_zero();
|
||||
|
||||
move_input.set(direction * move_factor.0);
|
||||
move_input.set(direction * move_factor.0);
|
||||
|
||||
if inputs.jump && grounding.is_grounded() {
|
||||
flags.jumping = true;
|
||||
flags.jump_count += 1;
|
||||
happy_feet::movement::jump(settings.jump_force, &mut velocity, &mut grounding, Dir3::Y)
|
||||
if inputs.jump && grounding.is_grounded() {
|
||||
flags.jumping = true;
|
||||
flags.jump_count += 1;
|
||||
happy_feet::movement::jump(settings.jump_force, &mut velocity, &mut grounding, Dir3::Y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ pub enum ControllerSet {
|
||||
ApplyControlsRun,
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[derive(Component, Reflect, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[reflect(Component)]
|
||||
pub enum SelectedController {
|
||||
Flying,
|
||||
#[default]
|
||||
@@ -35,8 +36,9 @@ pub fn plugin(app: &mut App) {
|
||||
#[cfg(feature = "client")]
|
||||
app.register_type::<LocalInputs>();
|
||||
|
||||
app.register_type::<SelectedController>();
|
||||
|
||||
app.init_resource::<LookDirMovement>();
|
||||
app.init_resource::<SelectedController>();
|
||||
|
||||
app.add_message::<ControllerSwitchEvent>()
|
||||
.add_message::<BackpackButtonPress>();
|
||||
@@ -48,8 +50,8 @@ pub fn plugin(app: &mut App) {
|
||||
app.configure_sets(
|
||||
FixedUpdate,
|
||||
(
|
||||
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController::Flying)),
|
||||
ControllerSet::ApplyControlsRun.run_if(resource_equals(SelectedController::Running)),
|
||||
ControllerSet::ApplyControlsFly,
|
||||
ControllerSet::ApplyControlsRun,
|
||||
)
|
||||
.chain()
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
@@ -64,6 +66,35 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Update, head_change.run_if(in_state(GameState::Playing)));
|
||||
}
|
||||
|
||||
#[derive(Reflect, Default, Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub enum ViewMode {
|
||||
#[default]
|
||||
Default,
|
||||
FreeMode,
|
||||
}
|
||||
|
||||
impl ViewMode {
|
||||
pub fn from_input(button: bool) -> Self {
|
||||
if button {
|
||||
Self::FreeMode
|
||||
} else {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_input(&mut self, button: bool) {
|
||||
let new = Self::from_input(button);
|
||||
*self = match (*self, new) {
|
||||
(Self::FreeMode, _) | (_, Self::FreeMode) => Self::FreeMode,
|
||||
_ => Self::Default,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_free(&self) -> bool {
|
||||
matches!(self, Self::FreeMode)
|
||||
}
|
||||
}
|
||||
|
||||
/// The continuous inputs of a client for a tick. The instant inputs are sent via messages like `BackpackTogglePressed`.
|
||||
#[derive(Component, Clone, Copy, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
@@ -75,7 +106,7 @@ pub struct Inputs {
|
||||
pub look_dir: Vec3,
|
||||
pub jump: bool,
|
||||
/// Determines if the camera can rotate freely around the player
|
||||
pub view_mode: bool,
|
||||
pub view_mode: ViewMode,
|
||||
pub trigger: bool,
|
||||
}
|
||||
|
||||
@@ -154,23 +185,24 @@ fn collect_player_inputs(
|
||||
}
|
||||
|
||||
fn head_change(
|
||||
//TODO: needs a 'LocalPlayer' at some point for multiplayer
|
||||
query: Query<(Entity, &ActiveHead), (Changed<ActiveHead>, With<Player>)>,
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &ActiveHead, &SelectedController), (Changed<ActiveHead>, With<Player>)>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
mut selected_controller: ResMut<SelectedController>,
|
||||
mut event_controller_switch: MessageWriter<ControllerSwitchEvent>,
|
||||
) {
|
||||
for (entity, head) in query.iter() {
|
||||
for (entity, head, selected_controller) in query.iter() {
|
||||
let stats = heads_db.head_stats(head.0);
|
||||
let controller = match stats.controls {
|
||||
HeadControls::Plane => SelectedController::Flying,
|
||||
HeadControls::Walk => SelectedController::Running,
|
||||
};
|
||||
|
||||
info!("player head changed: {} ({:?})", head.0, controller);
|
||||
|
||||
if *selected_controller != controller {
|
||||
event_controller_switch.write(ControllerSwitchEvent { controller: entity });
|
||||
|
||||
*selected_controller = controller;
|
||||
commands.entity(entity).insert(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +1,5 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
camera::{CameraState, MainCamera},
|
||||
global_observer,
|
||||
tb_entities::{CameraTarget, CutsceneCamera, CutsceneCameraMovementEnd},
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_trenchbroom::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Event, Serialize, Deserialize)]
|
||||
pub struct StartCutscene(pub String);
|
||||
|
||||
#[derive(Resource, Debug, Default)]
|
||||
enum CutsceneState {
|
||||
#[default]
|
||||
None,
|
||||
Playing {
|
||||
timer: Timer,
|
||||
camera_start: Transform,
|
||||
camera_end: Transform,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<CutsceneState>();
|
||||
app.add_systems(Update, update.run_if(in_state(GameState::Playing)));
|
||||
|
||||
global_observer!(app, on_start_cutscene);
|
||||
}
|
||||
|
||||
fn on_start_cutscene(
|
||||
trigger: On<StartCutscene>,
|
||||
mut cam_state: ResMut<CameraState>,
|
||||
mut cutscene_state: ResMut<CutsceneState>,
|
||||
cutscenes: Query<(&Transform, &CutsceneCamera, &Target), Without<MainCamera>>,
|
||||
cutscene_movement: Query<
|
||||
(&Transform, &CutsceneCameraMovementEnd, &Target),
|
||||
Without<MainCamera>,
|
||||
>,
|
||||
cam_target: Query<(&Transform, &CameraTarget), Without<MainCamera>>,
|
||||
) {
|
||||
let cutscene = trigger.event().0.clone();
|
||||
|
||||
cam_state.cutscene = true;
|
||||
|
||||
// asumes `name` and `targetname` are equal
|
||||
let Some((t, _, target)) = cutscenes
|
||||
.iter()
|
||||
.find(|(_, cutscene_camera, _)| cutscene == cutscene_camera.name)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let move_end = cutscene_movement
|
||||
.iter()
|
||||
.find(|(_, _, target)| cutscene == target.target.clone().unwrap_or_default())
|
||||
.map(|(t, _, _)| *t)
|
||||
.unwrap_or_else(|| *t);
|
||||
|
||||
let Some((target, _)) = cam_target.iter().find(|(_, camera_target)| {
|
||||
camera_target.targetname == target.target.clone().unwrap_or_default()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
*cutscene_state = CutsceneState::Playing {
|
||||
timer: Timer::from_seconds(2.0, TimerMode::Once),
|
||||
camera_start: t.looking_at(target.translation, Vec3::Y),
|
||||
camera_end: move_end.looking_at(target.translation, Vec3::Y),
|
||||
};
|
||||
}
|
||||
|
||||
fn update(
|
||||
mut cam_state: ResMut<CameraState>,
|
||||
mut cutscene_state: ResMut<CutsceneState>,
|
||||
mut cam: Query<&mut Transform, With<MainCamera>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if let CutsceneState::Playing {
|
||||
timer,
|
||||
camera_start,
|
||||
camera_end,
|
||||
} = &mut *cutscene_state
|
||||
{
|
||||
cam_state.cutscene = true;
|
||||
timer.tick(time.delta());
|
||||
|
||||
let t = Transform::from_translation(
|
||||
camera_start
|
||||
.translation
|
||||
.lerp(camera_end.translation, timer.fraction()),
|
||||
)
|
||||
.with_rotation(
|
||||
camera_start
|
||||
.rotation
|
||||
.lerp(camera_end.rotation, timer.fraction()),
|
||||
);
|
||||
|
||||
let _ = cam.single_mut().map(|mut cam| *cam = t);
|
||||
|
||||
if timer.is_finished() {
|
||||
cam_state.cutscene = false;
|
||||
*cutscene_state = CutsceneState::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ use crate::{
|
||||
protocol::{GltfSceneRoot, NetworkEnv, PlaySound},
|
||||
server_observer,
|
||||
tb_entities::SecretHead,
|
||||
utils::{
|
||||
billboards::Billboard, one_shot_force::OneShotImpulse, squish_animation::SquishAnimation,
|
||||
},
|
||||
utils::{Billboard, one_shot_force::OneShotImpulse, squish_animation::SquishAnimation},
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{ecs::relationship::RelatedSpawner, prelude::*};
|
||||
|
||||
@@ -13,16 +13,9 @@ use bevy::prelude::*;
|
||||
use bevy_replicon::prelude::FromClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod heads_ui;
|
||||
|
||||
pub static HEAD_COUNT: usize = 18;
|
||||
pub static HEAD_SLOTS: usize = 5;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct HeadsImages {
|
||||
pub heads: Vec<Handle<Image>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Serialize, Deserialize)]
|
||||
pub struct HeadState {
|
||||
pub head: usize,
|
||||
@@ -74,6 +67,10 @@ impl ActiveHeads {
|
||||
self.heads[self.current_slot]
|
||||
}
|
||||
|
||||
pub fn slot(&self) -> usize {
|
||||
self.current_slot
|
||||
}
|
||||
|
||||
pub fn use_ammo(&mut self, time: f32) {
|
||||
let Some(head) = &mut self.heads[self.current_slot] else {
|
||||
error!("cannot use ammo of empty head");
|
||||
@@ -177,15 +174,15 @@ impl ActiveHeads {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct HeadChanged(pub usize);
|
||||
#[derive(EntityEvent)]
|
||||
pub struct HeadChanged {
|
||||
pub entity: Entity,
|
||||
pub head: usize,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(heads_ui::plugin);
|
||||
|
||||
app.register_type::<ActiveHeads>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(
|
||||
@@ -198,15 +195,6 @@ pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_swap_backpack);
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, heads: Res<HeadsDatabase>) {
|
||||
// TODO: load via asset loader
|
||||
let heads = (0usize..HEAD_COUNT)
|
||||
.map(|i| asset_server.load(format!("ui/heads/{}.png", heads.head_key(i))))
|
||||
.collect();
|
||||
|
||||
commands.insert_resource(HeadsImages { heads });
|
||||
}
|
||||
|
||||
fn sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
|
||||
for (mut active_heads, hp) in query.iter_mut() {
|
||||
if active_heads.hp().get() != hp.get() {
|
||||
@@ -217,11 +205,10 @@ fn sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
|
||||
|
||||
fn reload(
|
||||
mut commands: Commands,
|
||||
mut active: Query<&mut ActiveHeads>,
|
||||
mut active: Query<(&mut ActiveHeads, &mut AnimationFlags), With<Player>>,
|
||||
time: Res<Time>,
|
||||
mut flags: Single<&mut AnimationFlags, With<Player>>,
|
||||
) {
|
||||
for mut active in active.iter_mut() {
|
||||
for (mut active, mut flags) in active.iter_mut() {
|
||||
if !active.reloading() {
|
||||
continue;
|
||||
}
|
||||
@@ -249,6 +236,7 @@ fn reload(
|
||||
fn on_select_active_head(
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
|
||||
// TODO: unify into just one message
|
||||
mut select_lefts: MessageReader<FromClient<SelectLeftPressed>>,
|
||||
mut select_rights: MessageReader<FromClient<SelectRightPressed>>,
|
||||
controllers: ClientToController,
|
||||
@@ -270,9 +258,10 @@ fn on_select_active_head(
|
||||
active_heads.current_slot = active_heads.selected_slot;
|
||||
hp.set_health(active_heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(HeadChanged(
|
||||
active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
));
|
||||
commands.trigger(HeadChanged {
|
||||
entity: player,
|
||||
head: active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,21 +282,25 @@ fn on_select_active_head(
|
||||
active_heads.current_slot = active_heads.selected_slot;
|
||||
hp.set_health(active_heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(HeadChanged(
|
||||
active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
));
|
||||
commands.trigger(HeadChanged {
|
||||
entity: player,
|
||||
head: active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_swap_backpack(
|
||||
trigger: On<FromClient<BackpackSwapEvent>>,
|
||||
clients: ClientToController,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
|
||||
mut query: Query<(Entity, &mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
|
||||
) {
|
||||
let player = clients.get_controller(trigger.client_id);
|
||||
|
||||
let backpack_slot = trigger.event().0;
|
||||
|
||||
let Ok((mut active_heads, mut hp, mut backpack)) = query.single_mut() else {
|
||||
let Ok((player, mut active_heads, mut hp, mut backpack)) = query.get_mut(player) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -326,7 +319,8 @@ fn on_swap_backpack(
|
||||
|
||||
hp.set_health(active_heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(HeadChanged(
|
||||
active_heads.heads[active_heads.selected_slot].unwrap().head,
|
||||
));
|
||||
commands.trigger(HeadChanged {
|
||||
entity: player,
|
||||
head: active_heads.heads[active_heads.selected_slot].unwrap().head,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::{
|
||||
billboards::Billboard,
|
||||
global_observer,
|
||||
physics_layers::GameLayer,
|
||||
player::Player,
|
||||
protocol::{GltfSceneRoot, PlaySound},
|
||||
squish_animation::SquishAnimation,
|
||||
utils::one_shot_force::OneShotImpulse,
|
||||
utils::{Billboard, one_shot_force::OneShotImpulse},
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -34,9 +34,9 @@ pub mod utils;
|
||||
pub mod water;
|
||||
|
||||
use crate::{
|
||||
config::NetworkingConfig,
|
||||
config::NetConfig,
|
||||
heads_database::{HeadDatabaseAsset, HeadsDatabase},
|
||||
protocol::{PlayerId, messages::AssignClientPlayer},
|
||||
protocol::{PlayerIdCounter, messages::AssignClientPlayer},
|
||||
tb_entities::SpawnPoint,
|
||||
};
|
||||
use avian3d::{PhysicsPlugins, prelude::TransformInterpolation};
|
||||
@@ -47,11 +47,12 @@ use bevy_common_assets::ron::RonAssetPlugin;
|
||||
use bevy_replicon::{RepliconPlugins, prelude::ClientId};
|
||||
use bevy_replicon_renet::RepliconRenetPlugins;
|
||||
use bevy_sprite3d::Sprite3dPlugin;
|
||||
use bevy_steamworks::SteamworksEvent;
|
||||
use bevy_trenchbroom::{
|
||||
TrenchBroomPlugins, config::TrenchBroomConfig, prelude::TrenchBroomPhysicsPlugin,
|
||||
};
|
||||
use bevy_trenchbroom_avian::AvianPhysicsBackend;
|
||||
use utils::{billboards, squish_animation};
|
||||
use utils::squish_animation;
|
||||
|
||||
pub const HEDZ_GREEN: Srgba = Srgba::rgb(0.0, 1.0, 0.0);
|
||||
pub const HEDZ_PURPLE: Srgba = Srgba::rgb(91. / 256., 4. / 256., 138. / 256.);
|
||||
@@ -118,16 +119,13 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(gates::plugin);
|
||||
app.add_plugins(platforms::plugin);
|
||||
app.add_plugins(movables::plugin);
|
||||
app.add_plugins(utils::billboards::plugin);
|
||||
app.add_plugins(aim::plugin);
|
||||
app.add_plugins(npc::plugin);
|
||||
app.add_plugins(keys::plugin);
|
||||
app.add_plugins(utils::squish_animation::plugin);
|
||||
app.add_plugins(camera::plugin);
|
||||
#[cfg(feature = "client")]
|
||||
app.add_plugins(client::plugin);
|
||||
app.add_plugins(control::plugin);
|
||||
app.add_plugins(cutscene::plugin);
|
||||
app.add_plugins(backpack::plugin);
|
||||
app.add_plugins(loading_assets::LoadingPlugin);
|
||||
app.add_plugins(loading_map::plugin);
|
||||
@@ -148,21 +146,21 @@ pub fn plugin(app: &mut App) {
|
||||
if cfg!(feature = "client") {
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Waiting),
|
||||
start_solo_client
|
||||
.run_if(|config: Res<NetworkingConfig>| config.server.is_none() && !config.host),
|
||||
start_solo_client.run_if(|config: Res<NetConfig>| config.is_singleplayer()),
|
||||
);
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Waiting),
|
||||
start_listen_server
|
||||
.run_if(|config: Res<NetworkingConfig>| config.server.is_none() && config.host),
|
||||
start_listen_server.run_if(|config: Res<NetConfig>| config.is_host()),
|
||||
);
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Waiting),
|
||||
start_client.run_if(|config: Res<NetworkingConfig>| config.server.is_some()),
|
||||
start_client.run_if(|config: Res<NetConfig>| config.is_client()),
|
||||
);
|
||||
} else {
|
||||
app.add_systems(OnEnter(GameState::Waiting), start_dedicated_server);
|
||||
}
|
||||
|
||||
app.add_systems(Update, log_steam_events);
|
||||
}
|
||||
|
||||
#[derive(Resource, Reflect, Debug)]
|
||||
@@ -192,18 +190,33 @@ pub enum GameState {
|
||||
Playing,
|
||||
}
|
||||
|
||||
fn log_steam_events(events: Option<MessageReader<SteamworksEvent>>) {
|
||||
let Some(mut events) = events else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in events.read() {
|
||||
let SteamworksEvent::CallbackResult(result) = event;
|
||||
info!("steam: {:?}", result);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_solo_client(
|
||||
commands: Commands,
|
||||
mut next: ResMut<NextState<GameState>>,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
mut assign_player_id: MessageWriter<AssignClientPlayer>,
|
||||
mut ids: ResMut<PlayerIdCounter>,
|
||||
) {
|
||||
next.set(GameState::Playing);
|
||||
|
||||
player::spawn(commands, ClientId::Server, query, heads_db);
|
||||
ids.reset();
|
||||
let id = ids.alloc();
|
||||
|
||||
assign_player_id.write(AssignClientPlayer(PlayerId { id: 0 }));
|
||||
player::spawn(commands, ClientId::Server, id, query, heads_db);
|
||||
|
||||
assign_player_id.write(AssignClientPlayer(id));
|
||||
}
|
||||
|
||||
fn start_listen_server(
|
||||
@@ -212,12 +225,16 @@ fn start_listen_server(
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
mut assign_player_id: MessageWriter<AssignClientPlayer>,
|
||||
mut ids: ResMut<PlayerIdCounter>,
|
||||
) {
|
||||
next.set(GameState::Hosting);
|
||||
|
||||
player::spawn(commands, ClientId::Server, query, heads_db);
|
||||
ids.reset();
|
||||
let id = ids.alloc();
|
||||
|
||||
assign_player_id.write(AssignClientPlayer(PlayerId { id: 0 }));
|
||||
player::spawn(commands, ClientId::Server, id, query, heads_db);
|
||||
|
||||
assign_player_id.write(AssignClientPlayer(id));
|
||||
}
|
||||
|
||||
fn start_client(mut next: ResMut<NextState<GameState>>) {
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
loading_assets::GameAssets,
|
||||
protocol::{PlaySound, is_server},
|
||||
tb_entities::EnemySpawn,
|
||||
utils::billboards::Billboard,
|
||||
utils::Billboard,
|
||||
};
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::PlayerTriggerState,
|
||||
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||
backpack::Backpack,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::{Cash, CashCollectEvent, CashInventory},
|
||||
cash::CashInventory,
|
||||
character::{AnimatedCharacter, HedzCharacter},
|
||||
control::{Inputs, LocalInputs, controller_common::PlayerCharacterController},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_drop::HeadDrops,
|
||||
heads::{ActiveHeads, HeadChanged, HeadState, heads_ui::UiActiveHeads},
|
||||
heads::{ActiveHeads, HeadChanged, HeadState},
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
npc::SpawnCharacter,
|
||||
protocol::{ClientHeadChanged, OwnedByClient, PlaySound, PlayerId},
|
||||
tb_entities::SpawnPoint,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
input::common_conditions::input_just_pressed,
|
||||
prelude::*,
|
||||
@@ -24,6 +23,7 @@ use bevy::{
|
||||
};
|
||||
use bevy_replicon::prelude::{ClientId, Replicated, SendMode, ServerTriggerExt, ToClients};
|
||||
use happy_feet::debug::DebugInput;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
@@ -32,7 +32,7 @@ pub struct Player;
|
||||
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(LocalInputs, BackpackUiState)]
|
||||
#[require(LocalInputs)]
|
||||
pub struct LocalPlayer;
|
||||
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
@@ -52,7 +52,6 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
collect_cash,
|
||||
setup_animations_marker_for_player,
|
||||
toggle_cursor_system.run_if(input_just_pressed(KeyCode::Escape)),
|
||||
)
|
||||
@@ -65,12 +64,21 @@ pub fn plugin(app: &mut App) {
|
||||
pub fn spawn(
|
||||
mut commands: Commands,
|
||||
owner: ClientId,
|
||||
id: PlayerId,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) -> Option<Entity> {
|
||||
let spawn = query.iter().next()?;
|
||||
|
||||
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
|
||||
// This offset helps prevent players from getting stuck inside each other on spawn and causing a perpetual
|
||||
// motion machine.
|
||||
let random_offset = Vec3::new(
|
||||
rand::thread_rng().gen_range(-0.01..0.01),
|
||||
0.0,
|
||||
rand::thread_rng().gen_range(-0.01..0.01),
|
||||
);
|
||||
let transform =
|
||||
Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.) + random_offset);
|
||||
|
||||
let id = commands
|
||||
.spawn((
|
||||
@@ -91,11 +99,9 @@ pub fn spawn(
|
||||
transform,
|
||||
Visibility::default(),
|
||||
PlayerCharacterController,
|
||||
PlayerId { id: 0 },
|
||||
id,
|
||||
),
|
||||
Backpack::default(),
|
||||
BackpackUiState::default(),
|
||||
UiActiveHeads::default(),
|
||||
Inputs::default(),
|
||||
Replicated,
|
||||
))
|
||||
@@ -131,9 +137,16 @@ pub fn spawn(
|
||||
fn on_kill(
|
||||
trigger: On<Kill>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
|
||||
mut query: Query<(
|
||||
Entity,
|
||||
&Transform,
|
||||
&ActiveHead,
|
||||
&mut ActiveHeads,
|
||||
&mut Hitpoints,
|
||||
)>,
|
||||
) {
|
||||
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.event().entity) else {
|
||||
let Ok((player, transform, active, mut heads, mut hp)) = query.get_mut(trigger.event().entity)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -142,31 +155,51 @@ fn on_kill(
|
||||
if let Some(new_head) = heads.loose_current() {
|
||||
hp.set_health(heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(HeadChanged(new_head));
|
||||
commands.trigger(HeadChanged {
|
||||
entity: player,
|
||||
head: new_head,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn on_update_head_mesh(
|
||||
trigger: On<HeadChanged>,
|
||||
mut commands: Commands,
|
||||
mesh_children: Single<&Children, With<PlayerBodyMesh>>,
|
||||
player_id: Query<&PlayerId, With<Player>>,
|
||||
children: Query<&Children>,
|
||||
player_body_mesh: Query<&PlayerBodyMesh>,
|
||||
animated_characters: Query<&AnimatedCharacter>,
|
||||
mut player: Single<&mut ActiveHead, With<Player>>,
|
||||
mut active_head: Query<&mut ActiveHead>,
|
||||
) -> Result {
|
||||
let animated_char = mesh_children
|
||||
.iter()
|
||||
.find(|child| animated_characters.contains(*child))
|
||||
.ok_or("tried to update head mesh before AnimatedCharacter was readded")?;
|
||||
let player_id = *(player_id.get(trigger.entity)?);
|
||||
|
||||
player.0 = trigger.0;
|
||||
let player_body_mesh = children
|
||||
.get(trigger.entity)?
|
||||
.iter()
|
||||
.find(|child| player_body_mesh.get(*child).is_ok())
|
||||
.unwrap();
|
||||
|
||||
let animated_character = children
|
||||
.get(player_body_mesh)?
|
||||
.iter()
|
||||
.find(|child| animated_characters.get(*child).is_ok())
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let mut active_head = active_head.get_mut(trigger.entity)?;
|
||||
active_head.0 = trigger.head;
|
||||
}
|
||||
|
||||
commands
|
||||
.entity(animated_char)
|
||||
.insert(AnimatedCharacter::new(trigger.0));
|
||||
.entity(animated_character)
|
||||
.insert(AnimatedCharacter::new(trigger.head));
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: ClientHeadChanged(trigger.0 as u64),
|
||||
message: ClientHeadChanged {
|
||||
player: player_id,
|
||||
head: trigger.head,
|
||||
},
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -198,33 +231,6 @@ fn toggle_cursor_system(mut window: Single<&mut CursorOptions, With<PrimaryWindo
|
||||
toggle_grab_cursor(&mut window);
|
||||
}
|
||||
|
||||
fn collect_cash(
|
||||
mut commands: Commands,
|
||||
mut collision_message_reader: MessageReader<CollisionStart>,
|
||||
query_player: Query<&Player>,
|
||||
query_cash: Query<&Cash>,
|
||||
) {
|
||||
for CollisionStart {
|
||||
collider1: e1,
|
||||
collider2: e2,
|
||||
..
|
||||
} in collision_message_reader.read()
|
||||
{
|
||||
let collect = if query_player.contains(*e1) && query_cash.contains(*e2) {
|
||||
Some(*e2)
|
||||
} else if query_player.contains(*e2) && query_cash.contains(*e1) {
|
||||
Some(*e1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(cash) = collect {
|
||||
commands.trigger(CashCollectEvent);
|
||||
commands.entity(cash).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_animations_marker_for_player(
|
||||
mut commands: Commands,
|
||||
animation_handles: Query<Entity, Added<AnimationGraphHandle>>,
|
||||
|
||||
@@ -76,6 +76,23 @@ impl From<NetworkedCollider> for Collider {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct PlayerIdCounter {
|
||||
next: u8,
|
||||
}
|
||||
|
||||
impl PlayerIdCounter {
|
||||
pub fn reset(&mut self) {
|
||||
self.next = 0;
|
||||
}
|
||||
|
||||
pub fn alloc(&mut self) -> PlayerId {
|
||||
let id = PlayerId { id: self.next };
|
||||
self.next += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// An ID, unique per player, inserted on the character controller. The `PlayerIdMap` maintains a mapping of ID -> controller entity
|
||||
/// on the server
|
||||
#[derive(Clone, Copy, Component, Hash, Reflect, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -171,6 +188,7 @@ pub enum GltfSceneRoot {
|
||||
Projectile(String),
|
||||
HeadDrop(String),
|
||||
Key,
|
||||
Cash,
|
||||
}
|
||||
|
||||
pub fn spawn_gltf_scene_roots(
|
||||
@@ -202,6 +220,7 @@ pub fn spawn_gltf_scene_roots(
|
||||
get_scene(gltf, 0)
|
||||
}
|
||||
GltfSceneRoot::Key => assets.mesh_key.clone(),
|
||||
GltfSceneRoot::Cash => assets.mesh_cash.clone(),
|
||||
};
|
||||
|
||||
commands
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use crate::protocol::PlayerId;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// TODO: remove in favour of client side change detection
|
||||
#[derive(Clone, Event, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ClientHeadChanged(pub u64);
|
||||
pub struct ClientHeadChanged {
|
||||
pub player: PlayerId,
|
||||
pub head: usize,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum PlaySound {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
animation::AnimationFlags,
|
||||
backpack::{Backpack, BackpackSwapEvent},
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::CashInventory,
|
||||
cash::{Cash, CashInventory},
|
||||
character::{AnimatedCharacter, HedzCharacter},
|
||||
control::{
|
||||
CashHealPressed, ClientInputs, ControllerSettings, Inputs, SelectLeftPressed,
|
||||
@@ -16,15 +16,14 @@ use crate::{
|
||||
cutscene::StartCutscene,
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
heads::{ActiveHeads, heads_ui::UiActiveHeads},
|
||||
heads::ActiveHeads,
|
||||
hitpoints::Hitpoints,
|
||||
npc::Npc,
|
||||
platforms::ActivePlatform,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
tick::GameTick,
|
||||
utils::{
|
||||
auto_rotate::AutoRotation, billboards::Billboard, squish_animation::SquishAnimation,
|
||||
trail::SpawnTrail,
|
||||
Billboard, auto_rotate::AutoRotation, squish_animation::SquishAnimation, trail::SpawnTrail,
|
||||
},
|
||||
};
|
||||
use avian3d::prelude::{
|
||||
@@ -74,6 +73,7 @@ pub fn plugin(app: &mut App) {
|
||||
app.register_type::<TbMapIdCounter>();
|
||||
app.register_type::<TbMapEntityMapping>();
|
||||
|
||||
app.init_resource::<PlayerIdCounter>();
|
||||
app.init_resource::<PlayerIdMap>();
|
||||
app.init_resource::<TbMapIdCounter>();
|
||||
app.init_resource::<TbMapEntityMapping>();
|
||||
@@ -106,7 +106,7 @@ pub fn plugin(app: &mut App) {
|
||||
.replicate::<SquishAnimation>()
|
||||
.replicate_once::<Transform>()
|
||||
.replicate_once::<SpawnTrail>()
|
||||
.replicate::<UiActiveHeads>()
|
||||
.replicate_once::<Cash>()
|
||||
.replicate_as::<Visibility, SerVisibility>();
|
||||
|
||||
app.replicate_once::<ThrownProjectile>()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::{
|
||||
GameState, global_observer,
|
||||
GameState,
|
||||
config::NetConfig,
|
||||
global_observer,
|
||||
heads_database::HeadsDatabase,
|
||||
player::ClientPlayerId,
|
||||
protocol::{ClientEnteredPlaying, PlayerId, SetGameTick, messages::AssignClientPlayer},
|
||||
protocol::{ClientEnteredPlaying, PlayerIdCounter, SetGameTick, messages::AssignClientPlayer},
|
||||
tb_entities::SpawnPoint,
|
||||
tick::GameTick,
|
||||
};
|
||||
@@ -16,12 +18,8 @@ use bevy_replicon::{
|
||||
};
|
||||
use bevy_replicon_renet::{
|
||||
RenetChannelsExt,
|
||||
netcode::{NetcodeServerTransport, ServerAuthentication},
|
||||
renet::{ConnectionConfig, RenetServer},
|
||||
};
|
||||
use std::{
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
time::SystemTime,
|
||||
steam::SteamServerTransport,
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -38,12 +36,14 @@ pub fn plugin(app: &mut App) {
|
||||
fn on_client_playing(
|
||||
trigger: On<FromClient<ClientEnteredPlaying>>,
|
||||
commands: Commands,
|
||||
clients: Query<&ClientPlayerId>,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) -> Result {
|
||||
info!("client has entered playing gamestate");
|
||||
|
||||
crate::player::spawn(commands, trigger.client_id, query, heads_db)
|
||||
let id = clients.get(trigger.client_id.entity().unwrap()).unwrap();
|
||||
crate::player::spawn(commands, trigger.client_id, id.0, query, heads_db)
|
||||
.ok_or("failed to spawn player")?;
|
||||
|
||||
Ok(())
|
||||
@@ -57,6 +57,8 @@ fn open_renet_server(
|
||||
mut commands: Commands,
|
||||
channels: Res<RepliconChannels>,
|
||||
mut next: ResMut<NextState<GameState>>,
|
||||
steam_client: Option<Res<bevy_steamworks::Client>>,
|
||||
config: Res<NetConfig>,
|
||||
) -> Result<(), BevyError> {
|
||||
info!("opening server");
|
||||
|
||||
@@ -69,22 +71,48 @@ fn open_renet_server(
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||
let port = 31111;
|
||||
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, port))?;
|
||||
let server_config = bevy_replicon_renet::netcode::ServerConfig {
|
||||
current_time,
|
||||
max_clients: 1,
|
||||
protocol_id: 0,
|
||||
authentication: ServerAuthentication::Unsecure,
|
||||
public_addresses: Default::default(),
|
||||
};
|
||||
let transport = NetcodeServerTransport::new(server_config, socket)?;
|
||||
if let NetConfig::SteamHost = *config {
|
||||
let Some(steam_client) = steam_client else {
|
||||
return Err("Steam client not found".into());
|
||||
};
|
||||
|
||||
commands.insert_resource(server);
|
||||
commands.insert_resource(transport);
|
||||
let steam_config = bevy_replicon_renet::steam::SteamServerConfig {
|
||||
access_permission: bevy_replicon_renet::steam::AccessPermission::FriendsOnly,
|
||||
max_clients: 16,
|
||||
};
|
||||
|
||||
info!("hosting a server on port {port}");
|
||||
let client = (**steam_client).clone();
|
||||
let transport = SteamServerTransport::new(client, steam_config)?;
|
||||
|
||||
commands.queue(|w: &mut World| {
|
||||
w.insert_resource(server);
|
||||
w.insert_non_send_resource(transport);
|
||||
});
|
||||
|
||||
info!("hosting server: steam");
|
||||
} else if let NetConfig::NetcodeHost { port } = *config {
|
||||
use std::time::SystemTime;
|
||||
|
||||
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||
let socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, port))?;
|
||||
|
||||
let server_config = bevy_replicon_renet::netcode::ServerConfig {
|
||||
current_time,
|
||||
max_clients: 8,
|
||||
protocol_id: 0,
|
||||
authentication: bevy_replicon_renet::netcode::ServerAuthentication::Unsecure,
|
||||
public_addresses: Default::default(),
|
||||
};
|
||||
let transport =
|
||||
bevy_replicon_renet::netcode::NetcodeServerTransport::new(server_config, socket)?;
|
||||
|
||||
commands.insert_resource(server);
|
||||
commands.insert_resource(transport);
|
||||
|
||||
info!("hosting server: netcode on port {port}");
|
||||
} else {
|
||||
return Err("Invalid configuration, choose either steam or netcode".into());
|
||||
}
|
||||
|
||||
next.set(GameState::Playing);
|
||||
|
||||
@@ -100,16 +128,17 @@ fn on_connected(
|
||||
game_tick: Res<GameTick>,
|
||||
mut commands: Commands,
|
||||
mut assign_id: MessageWriter<ToClients<AssignClientPlayer>>,
|
||||
mut ids: ResMut<PlayerIdCounter>,
|
||||
) {
|
||||
let client = trigger.event_target();
|
||||
info!("{client} connected to server!");
|
||||
|
||||
let id = ClientPlayerId(PlayerId { id: 0 });
|
||||
commands.entity(client).insert(id);
|
||||
let id = ids.alloc();
|
||||
commands.entity(client).insert(ClientPlayerId(id));
|
||||
|
||||
assign_id.write(ToClients {
|
||||
mode: SendMode::Direct(ClientId::Client(trigger.entity)),
|
||||
message: AssignClientPlayer(id.0),
|
||||
message: AssignClientPlayer(id),
|
||||
});
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
cash::Cash,
|
||||
loading_assets::GameAssets,
|
||||
physics_layers::GameLayer,
|
||||
protocol::{
|
||||
@@ -152,30 +151,8 @@ impl EnemySpawn {
|
||||
|
||||
#[point_class(base(Transform), model({ "path": "models/cash.glb" }))]
|
||||
#[derive(Default)]
|
||||
#[component(on_add = Self::on_add)]
|
||||
pub struct CashSpawn {}
|
||||
|
||||
impl CashSpawn {
|
||||
fn on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
|
||||
let Some(assets) = world.get_resource::<GameAssets>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mesh = assets.mesh_cash.clone();
|
||||
|
||||
world.commands().entity(entity).insert((
|
||||
Name::new("cash"),
|
||||
SceneRoot(mesh),
|
||||
Cash,
|
||||
Collider::cuboid(2., 3.0, 2.),
|
||||
CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::ALL),
|
||||
RigidBody::Static,
|
||||
CollisionEventsEnabled,
|
||||
Sensor,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[point_class(base(Transform), model({ "path": "models/head_drop.glb" }))]
|
||||
#[derive(Default)]
|
||||
pub struct SecretHead {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod auto_rotate;
|
||||
pub mod billboards;
|
||||
pub mod cooldown;
|
||||
pub mod debounce;
|
||||
pub mod explosions;
|
||||
@@ -14,7 +13,18 @@ use bevy::prelude::*;
|
||||
pub use cooldown::Cooldown;
|
||||
pub use debounce::Debounce;
|
||||
pub(crate) use observers::global_observer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Reflect, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[reflect(Component)]
|
||||
pub enum Billboard {
|
||||
#[default]
|
||||
All,
|
||||
XZ,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<Billboard>();
|
||||
|
||||
app.add_plugins(one_shot_force::plugin);
|
||||
}
|
||||
|
||||
4
justfile
4
justfile
@@ -13,7 +13,7 @@ run *args:
|
||||
RUST_BACKTRACE=1 cargo r {{ client_args }} -- {{ args }}
|
||||
|
||||
server:
|
||||
RUST_BACKTRACE=1 cargo r {{ server_args }}
|
||||
RUST_BACKTRACE=1 cargo r {{ server_args }} -- --netcode-host
|
||||
|
||||
dbg *args:
|
||||
RUST_BACKTRACE=1 cargo r {{ client_args }} --features dbg -- {{ args }}
|
||||
@@ -22,7 +22,7 @@ dbg-server:
|
||||
RUST_BACKTRACE=1 cargo r {{ server_args }} --features dbg
|
||||
|
||||
sort:
|
||||
cargo sort --check --workspace
|
||||
cargo sort --workspace
|
||||
|
||||
check:
|
||||
cargo sort --check --workspace
|
||||
|
||||
Reference in New Issue
Block a user