Compare commits
22 Commits
use-remade
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a7239b3e83 | |||
| 16b08e2547 | |||
| 6ed54dfdc7 | |||
|
|
dd01b03526 | ||
|
|
da5c0f8fb7 | ||
| 375b8a5b46 | |||
| 89e6102b06 | |||
| e22fa8d134 | |||
| 181b617620 | |||
| 56ca801992 | |||
| 16cd95ae02 | |||
| fcb13eed31 | |||
|
|
0735c429ca | ||
|
|
c3c5ae6dfb | ||
|
|
cc7e2aae70 | ||
|
|
dbcd822b50 | ||
| ac8c834f2f | |||
| f35275ab9f | |||
| 3901ee1174 | |||
|
|
7d280af821 | ||
| 7ea9046414 | |||
|
|
f6fa9ce1e4 |
2
.github/workflows/archive_steamos.yml
vendored
2
.github/workflows/archive_steamos.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded ./
|
||||
tar -czf steamos.tar.gz hedz_reloaded
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: steamos.tar.gz
|
||||
path: ./steamos.tar.gz
|
||||
|
||||
2
.github/workflows/archive_win.yml
vendored
2
.github/workflows/archive_win.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
cp target/x86_64-pc-windows-msvc/release/hedz_reloaded.exe ./
|
||||
tar -czf win.tar.gz hedz_reloaded.exe
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: win.tar.gz
|
||||
path: ./win.tar.gz
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -14,8 +14,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cargo-bins/cargo-binstall@main
|
||||
|
||||
if: runner.os == 'linux'
|
||||
- uses: extractions/setup-just@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
2
.github/workflows/ci_debug.yml
vendored
2
.github/workflows/ci_debug.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
cp target/x86_64-unknown-linux-gnu/debug/hedz_reloaded target/x86_64-unknown-linux-gnu/debug/hedz_reloaded_server ./
|
||||
tar -czf steamos-debug.tar.gz hedz_reloaded hedz_reloaded_server
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: steamos-debug.tar.gz
|
||||
path: ./steamos-debug.tar.gz
|
||||
|
||||
4
.github/workflows/steam_alpha.yml
vendored
4
.github/workflows/steam_alpha.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
tar -czf hedz-macos.tar.gz hedz_reloaded
|
||||
ls -lisah hedz-macos.tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: hedz-macos
|
||||
path: ./hedz-macos.tar.gz
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded ./
|
||||
tar -czf steamos.tar.gz hedz_reloaded
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: steamos.tar.gz
|
||||
path: ./steamos.tar.gz
|
||||
|
||||
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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::*};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user