diff --git a/.gitignore b/.gitignore index 75d707f..3292612 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ umodel* *.csv settings.txt /collision_meshes +/cache diff --git a/Cargo.lock b/Cargo.lock index 7f14871..7caaf67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -171,6 +182,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.4.0" @@ -1210,6 +1230,15 @@ dependencies = [ "winit", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1265,6 +1294,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -1335,6 +1373,27 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "calloop" version = "0.12.4" @@ -1396,6 +1455,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clipboard-win" version = "5.4.0" @@ -1567,6 +1636,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -1610,6 +1703,16 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cursor-icon" version = "1.1.0" @@ -1639,6 +1742,43 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "4.0.0" @@ -1665,6 +1805,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1945,6 +2096,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -2157,6 +2318,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -2247,6 +2417,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2433,12 +2612,28 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2600,6 +2795,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -2917,6 +3118,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3004,6 +3215,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "pp-rs" version = "0.2.1" @@ -3013,6 +3230,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "presser" version = "0.3.1" @@ -3073,6 +3296,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -3081,6 +3316,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "range-alloc" @@ -3187,9 +3425,8 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" [[package]] name = "rlviser" -version = "0.7.12" +version = "0.7.13" dependencies = [ - "ahash", "bevy", "bevy_atmosphere", "bevy_egui", @@ -3197,8 +3434,10 @@ dependencies = [ "bevy_framepace", "bevy_mod_picking", "bevy_vector_shapes", + "bincode", "byteorder", "crossbeam-channel", + "image", "include-flate", "itertools", "once_cell", @@ -3208,6 +3447,7 @@ dependencies = [ "serde_json", "thiserror", "walkdir", + "zip", ] [[package]] @@ -3341,6 +3581,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3450,6 +3701,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.3" @@ -3541,6 +3798,25 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3703,6 +3979,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4591,3 +4873,94 @@ dependencies = [ "quote", "syn 2.0.71", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "zip" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "rand", + "sha1", + "thiserror", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.12+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 673d995..7d41a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -version = "0.7.12" +version = "0.7.13" name = "rlviser" edition = "2021" publish = false @@ -20,11 +20,13 @@ serde = { version = "1.0.160", features = ["derive", "rc"] } serde_json = "1.0.96" walkdir = "2.3.3" thiserror = "1.0.50" -ahash = "0.8.6" radsort = "0.1.0" rust_search = "2.1.0" crossbeam-channel = "0.5.12" itertools = "0.13.0" +bincode = "1.3.3" +zip = "2.1.3" +image = { version = "0.25.1", features = ["tga"], default-features = false } [dependencies.bevy] version = "0.14" diff --git a/README.md b/README.md index 1543861..9f440f6 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,6 @@ Any language can communicate with the visualizer by sending UDP packets in the c ![image](https://github.com/VirxEC/rlviser/assets/35614515/47613661-754a-4549-bcef-13df399645be) -### First-time Launch Setup - -On launch, RLViser looks for `umodel.exe` or `umodel` binaries in the same directory - it will pull the assets needed for you. - -You should have [UModel](https://www.gildor.org/en/projects/umodel) so the visualizer can uncook the game assets into the `assets/` directory. - -Precompiled versions of UModel for Windows and Linux are available on the website linked above, but Linux users may have to compile it themself. - -If you don't include UModel, the visualizer will use some minimalist default assets. -Certain things may not be present due to the lack of assets, but the field, cars, and ball are visible. - ### Usage To see an example of how to communicate with the visualizer, see the [example](https://github.com/VirxEC/rocketsim-rs/blob/master/examples/rlviser_socket.rs) in the [rocketsim-rs](https://github.com/VirxEC/rocketsim-rs) repository. diff --git a/cache.zip b/cache.zip new file mode 100644 index 0000000..5ba789f Binary files /dev/null and b/cache.zip differ diff --git a/default_assets/hoops/hoops_corner_ids.bin b/default_assets/hoops/hoops_corner_ids.bin deleted file mode 100644 index a6de0fb..0000000 Binary files a/default_assets/hoops/hoops_corner_ids.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_corner_vertices.bin b/default_assets/hoops/hoops_corner_vertices.bin deleted file mode 100644 index ae866c6..0000000 Binary files a/default_assets/hoops/hoops_corner_vertices.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_net_ids.bin b/default_assets/hoops/hoops_net_ids.bin deleted file mode 100644 index a894b98..0000000 Binary files a/default_assets/hoops/hoops_net_ids.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_net_vertices.bin b/default_assets/hoops/hoops_net_vertices.bin deleted file mode 100644 index 429a1f7..0000000 Binary files a/default_assets/hoops/hoops_net_vertices.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_ramps_0_ids.bin b/default_assets/hoops/hoops_ramps_0_ids.bin deleted file mode 100644 index 9823072..0000000 Binary files a/default_assets/hoops/hoops_ramps_0_ids.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_ramps_0_vertices.bin b/default_assets/hoops/hoops_ramps_0_vertices.bin deleted file mode 100644 index 6aafb9f..0000000 Binary files a/default_assets/hoops/hoops_ramps_0_vertices.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_ramps_1_ids.bin b/default_assets/hoops/hoops_ramps_1_ids.bin deleted file mode 100644 index 4f9221e..0000000 Binary files a/default_assets/hoops/hoops_ramps_1_ids.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_ramps_1_vertices.bin b/default_assets/hoops/hoops_ramps_1_vertices.bin deleted file mode 100644 index ee0ec40..0000000 Binary files a/default_assets/hoops/hoops_ramps_1_vertices.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_rim_ids.bin b/default_assets/hoops/hoops_rim_ids.bin deleted file mode 100644 index 33a775f..0000000 Binary files a/default_assets/hoops/hoops_rim_ids.bin and /dev/null differ diff --git a/default_assets/hoops/hoops_rim_vertices.bin b/default_assets/hoops/hoops_rim_vertices.bin deleted file mode 100644 index bc1d766..0000000 Binary files a/default_assets/hoops/hoops_rim_vertices.bin and /dev/null differ diff --git a/default_assets/standard/standard_corner_ids.bin b/default_assets/standard/standard_corner_ids.bin deleted file mode 100644 index 62c5eb2..0000000 Binary files a/default_assets/standard/standard_corner_ids.bin and /dev/null differ diff --git a/default_assets/standard/standard_corner_vertices.bin b/default_assets/standard/standard_corner_vertices.bin deleted file mode 100644 index e13b11a..0000000 Binary files a/default_assets/standard/standard_corner_vertices.bin and /dev/null differ diff --git a/default_assets/standard/standard_goal_ids.bin b/default_assets/standard/standard_goal_ids.bin deleted file mode 100644 index a1e83d5..0000000 Binary files a/default_assets/standard/standard_goal_ids.bin and /dev/null differ diff --git a/default_assets/standard/standard_goal_vertices.bin b/default_assets/standard/standard_goal_vertices.bin deleted file mode 100644 index 6dfc975..0000000 Binary files a/default_assets/standard/standard_goal_vertices.bin and /dev/null differ diff --git a/default_assets/standard/standard_ramps_0_ids.bin b/default_assets/standard/standard_ramps_0_ids.bin deleted file mode 100644 index c3c30dd..0000000 Binary files a/default_assets/standard/standard_ramps_0_ids.bin and /dev/null differ diff --git a/default_assets/standard/standard_ramps_0_vertices.bin b/default_assets/standard/standard_ramps_0_vertices.bin deleted file mode 100644 index 423d3df..0000000 Binary files a/default_assets/standard/standard_ramps_0_vertices.bin and /dev/null differ diff --git a/default_assets/standard/standard_ramps_1_ids.bin b/default_assets/standard/standard_ramps_1_ids.bin deleted file mode 100644 index 3970397..0000000 Binary files a/default_assets/standard/standard_ramps_1_ids.bin and /dev/null differ diff --git a/default_assets/standard/standard_ramps_1_vertices.bin b/default_assets/standard/standard_ramps_1_vertices.bin deleted file mode 100644 index 42d2b23..0000000 Binary files a/default_assets/standard/standard_ramps_1_vertices.bin and /dev/null differ diff --git a/src/assets.rs b/src/assets.rs index c6178ad..3771e12 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,36 +1,31 @@ use crate::{ mesh::{MeshBuilder, MeshBuilderError}, rocketsim::Team, + settings::cache_handler::{get_default_mesh_cache, get_material_cache, get_mesh_cache, get_texture_cache}, }; use bevy::{ asset::{io::Reader, AssetLoader, AsyncReadExt}, color::palettes::css, prelude::*, + render::renderer::RenderDevice, utils::ConditionalSendFuture, }; use byteorder::{LittleEndian, ReadBytesExt}; use once_cell::sync::Lazy; -use rust_search::{similarity_sort, SearchBuilder}; use std::{ collections::HashMap, ffi::OsStr, - fs, - io::{self, Read, Write}, - panic, - path::{Path, MAIN_SEPARATOR}, - process::{Command, Stdio}, + io::{self, Read}, + path::Path, sync::Mutex, }; use thiserror::Error; -use walkdir::WalkDir; pub struct AssetsLoaderPlugin; impl Plugin for AssetsLoaderPlugin { fn build(&self, app: &mut App) { - app.register_asset_loader(PskxLoader) - .init_asset::() - .add_systems(Startup, load_assets); + app.register_asset_loader(PskxLoader).init_asset::(); } } @@ -39,23 +34,29 @@ pub struct CarWheelMesh { pub mesh: Handle, } -fn load_assets(mut commands: Commands, assets: Res, mut meshes: ResMut>) { +pub fn load_assets( + mut commands: Commands, + assets: Res, + mut meshes: ResMut>, + mut images: ResMut>, + render_device: Option>, +) { commands.insert_resource(CarWheelMesh { - mesh: assets.load("WHEEL_Star/StaticMesh3/WHEEL_Star_SM.pskx"), + mesh: get_default_mesh_cache("WHEEL_Star/StaticMesh3/WHEEL_Star_SM.pskx", &assets, &mut meshes), }); commands.insert_resource(BoostPickupGlows { - small: assets.load("Pickup_Boost/StaticMesh3/BoostPad_Small_02_SM.pskx"), + small: get_default_mesh_cache("Pickup_Boost/StaticMesh3/BoostPad_Small_02_SM.pskx", &assets, &mut meshes), small_hitbox: meshes.add(Cylinder::new(144. / 2., 165.)), - large: assets.load("Pickup_Boost/StaticMesh3/BoostPad_Large_Glow.pskx"), + large: get_default_mesh_cache("Pickup_Boost/StaticMesh3/BoostPad_Large_Glow.pskx", &assets, &mut meshes), large_hitbox: meshes.add(Cylinder::new(208. / 2., 168.)), }); commands.insert_resource(BallAssets { - ball_diffuse: assets.load("Ball_Default_Textures/Texture2D/Ball_Default00_D.tga"), - // ball_normal: assets.load("Ball_Default_Textures/Texture2D/Ball_Default00_N.tga"), - // ball_occlude: assets.load("Ball_Default_Textures/Texture2D/Ball_Default00_RGB.tga"), - ball: assets.load("Ball_Default/StaticMesh3/Ball_DefaultBall00.pskx"), + ball_diffuse: get_texture_cache("Ball_Default00_D", &assets, &mut images, render_device.as_deref()), + // ball_normal: get_texture_cache("Ball_Default00_N", &assets), + // ball_occlude: get_texture_cache("Ball_Default00_RGB", &assets), + ball: get_default_mesh_cache("Ball_Default/StaticMesh3/Ball_DefaultBall00.pskx", &assets, &mut meshes), }); } @@ -149,37 +150,13 @@ pub fn get_mesh_info(name: &str, meshes: &mut Assets) -> Option Handle { - let mut assets_path = String::from("assets"); - assets_path.push(MAIN_SEPARATOR); - - let path = WalkDir::new("assets") - .into_iter() - .flatten() - .find(|x| x.file_name().to_string_lossy() == format!("{name}.tga")) - .unwrap() - .path() - .to_string_lossy() - .to_string() - .replace(&assets_path, ""); - - asset_server.load(path) + get_mesh_cache(cache_path, asset_path, name, meshes) } const DOUBLE_SIDED_MATS: [&str; 28] = [ @@ -261,6 +238,8 @@ fn retreive_material( asset_server: &AssetServer, base_color: Color, side: Option, + images: &mut Assets, + render_device: Option<&RenderDevice>, ) -> Option { if name.is_empty() { return None; @@ -291,47 +270,9 @@ fn retreive_material( } } - let path = format!("./assets/{pre_path}.mat"); - let Ok(mat_file) = fs::read_to_string(&path) else { - error!("Failed to read {path} ({name})"); - return None; - }; - - let props = format!("./assets/{pre_path}.props.txt"); - let Ok(props_file) = fs::read_to_string(props) else { - error!("Failed to read {path} ({name})"); - return None; - }; - - let mut diffuse = None; - let mut normal = None; - let mut other = Vec::new(); - - for line in mat_file.lines() { - // split at the first "=" - let mut split = line.split('='); - if let Some(key) = split.next() { - let Some(value) = split.next() else { - error!("No value for {key} in {path}"); - continue; - }; - - match key { - "Diffuse" => { - diffuse = Some(value); - } - "Normal" => { - normal = Some(value); - } - "Other[0]" => { - other.push(value); - } - x => { - warn!("Unknown key {x} is {value} in {path} ({name})"); - } - } - } - } + let file_name = pre_path.split('/').last().unwrap(); + let cache_path = format!("./cache/material/{file_name}.bin"); + let mesh_material = get_material_cache(cache_path, pre_path, name)?; let mut material = StandardMaterial { base_color, @@ -339,72 +280,36 @@ fn retreive_material( ..default() }; - let mut alpha_mode = None; - let mut mask_clip_value = 0.333; - let mut double_sided = None; - - for line in props_file.lines() { - let mut split = line.split(" = "); - if let Some(key) = split.next() { - let Some(value) = split.next() else { - continue; - }; - - if key == "TwoSided" { - double_sided = Some(value == "true"); - } else if key == "BlendMode" { - alpha_mode = match value { - "BLEND_Opaque (0)" => Some(AlphaMode::Opaque), - "BLEND_Masked (1)" => Some(AlphaMode::Mask(mask_clip_value)), - "BLEND_Translucent (2)" => Some(AlphaMode::Blend), - "BLEND_Additive (3)" => Some(AlphaMode::Add), - _ => { - error!("Unknown blend mode {value} in {path} ({name})"); - None - } - }; - } else if key == "OpacityMaskClipValue" { - if let Ok(mask_value) = value.parse() { - mask_clip_value = mask_value; - - if let Some(AlphaMode::Mask(_)) = alpha_mode { - alpha_mode = Some(AlphaMode::Mask(mask_clip_value)); - } - } - } - } - } - - if let Some(alpha_mode) = alpha_mode { - material.alpha_mode = alpha_mode; + if let Some(alpha_mode) = mesh_material.alpha_mode { + material.alpha_mode = alpha_mode.into(); } else if TRANSPARENT_MATS.contains(&name) { material.alpha_mode = AlphaMode::Blend; } else if ADD_MATS.contains(&name) { material.alpha_mode = AlphaMode::Add; } - if double_sided.unwrap_or_default() || DOUBLE_SIDED_MATS.contains(&name) { + if mesh_material.double_sided || DOUBLE_SIDED_MATS.contains(&name) { material.cull_mode = None; material.double_sided = true; } - if let Some(texture_name) = diffuse { + if let Some(texture_name) = &mesh_material.diffuse { debug!("Found texture for {name}"); if texture_name == "ForcefieldHex" { material.base_color = Color::srgba(0.3, 0.3, 0.3, 0.3); } - material.base_color_texture = Some(load_texture(texture_name, asset_server)); + material.base_color_texture = Some(get_texture_cache(texture_name, asset_server, images, render_device)); } - for texture_name in other { + for texture_name in mesh_material.other { // idealy, the textures would be combined - if diffuse.is_none() { - material.base_color_texture = Some(load_texture(texture_name, asset_server)); + if mesh_material.diffuse.is_none() { + material.base_color_texture = Some(get_texture_cache(&texture_name, asset_server, images, render_device)); } } - if let Some(texture_name) = normal { - material.normal_map_texture = Some(load_texture(texture_name, asset_server)); + if let Some(texture_name) = mesh_material.normal { + material.normal_map_texture = Some(get_texture_cache(&texture_name, asset_server, images, render_device)); } Some(material) @@ -502,6 +407,8 @@ pub fn get_material( asset_server: &AssetServer, base_color: Option, side: Option, + images: &mut Assets, + render_device: Option<&RenderDevice>, ) -> Handle { let mut material_names = MATERIALS.lock().unwrap(); @@ -518,7 +425,7 @@ pub fn get_material( .entry(key) .or_insert_with(|| { materials.add( - retreive_material(name, asset_server, base_color, side).unwrap_or(StandardMaterial { + retreive_material(name, asset_server, base_color, side, images, render_device).unwrap_or(StandardMaterial { base_color, metallic: 0.1, cull_mode: None, @@ -687,7 +594,12 @@ impl AssetLoader for PskxLoader { reader.read_to_end(&mut bytes).await?; let asset_name = load_context.path().file_name().and_then(OsStr::to_str).unwrap(); - Ok(MeshBuilder::from_pskx(asset_name, &bytes)?.build_mesh(1.)) + let mesh = MeshBuilder::from_pskx(asset_name, &bytes)?; + + let cache_path = format!("./cache/mesh/{}.bin", asset_name.trim_end_matches(".pskx")); + mesh.create_cache(Path::new(&cache_path)); + + Ok(mesh.build_mesh()) }) } @@ -696,178 +608,191 @@ impl AssetLoader for PskxLoader { } } -const CANT_FIND_FOLDER: &str = "Couldn't find 'RocketLeague.exe' on your system! Please manually create the file 'assets.path' and add the path in plain text to your 'rocketleague/TAGame/CookedPCConsole' folder. This is needed for UModel to work."; -const UMODEL: &str = if cfg!(windows) { ".\\umodel.exe" } else { "./umodel" }; -const OUT_DIR: &str = "./assets/"; -const OUT_DIR_VER: &str = "./assets/files.txt"; +#[cfg(debug_assertions)] +pub mod umodel { + use bevy::prelude::*; + use rust_search::{similarity_sort, SearchBuilder}; + use std::{ + fs, + io::{self, Write}, + panic, + path::Path, + process::{Command, Stdio}, + }; -fn find_input_dir() -> String { - println!("Couldn't find 'assets.path' file in your base folder!"); - print!("Try to automatically find the path? (y/n): "); + const CANT_FIND_FOLDER: &str = "Couldn't find 'RocketLeague.exe' on your system! Please manually create the file 'assets.path' and add the path in plain text to your 'rocketleague/TAGame/CookedPCConsole' folder. This is needed for UModel to work."; + const UMODEL: &str = if cfg!(windows) { ".\\umodel.exe" } else { "./umodel" }; + const OUT_DIR: &str = "./assets/"; + const OUT_DIR_VER: &str = "./assets/files.txt"; - io::stdout().flush().unwrap(); + fn find_input_dir() -> String { + println!("Couldn't find 'assets.path' file in your base folder!"); + print!("Try to automatically find the path? (y/n): "); - let mut input = String::new(); - io::stdin().read_line(&mut input).unwrap(); + io::stdout().flush().unwrap(); - if input.trim().to_lowercase() != "y" { - panic!("{CANT_FIND_FOLDER}"); - } + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); - println!("Searching system for 'RocketLeague.exe'..."); + if input.trim().to_lowercase() != "y" { + panic!("{CANT_FIND_FOLDER}"); + } - let search_input = "RocketLeague"; - let start_dir = if cfg!(windows) { "C:\\" } else { "~" }; + println!("Searching system for 'RocketLeague.exe'..."); - let mut search = SearchBuilder::default() - .location(start_dir) - .search_input(search_input) - .ext("exe") - .strict() - .hidden() - .build() - .collect(); + let search_input = "RocketLeague"; + let start_dir = if cfg!(windows) { "C:\\" } else { "~" }; - similarity_sort(&mut search, search_input); + let mut search = SearchBuilder::default() + .location(start_dir) + .search_input(search_input) + .ext("exe") + .strict() + .hidden() + .build() + .collect(); - if search.is_empty() { - panic!("{CANT_FIND_FOLDER}"); - } + similarity_sort(&mut search, search_input); - let mut input = String::new(); + if search.is_empty() { + panic!("{CANT_FIND_FOLDER}"); + } - let mut game_path = None; + let mut input = String::new(); - if search.len() == 1 { - println!("Found (1) result!"); - } else { - println!("Found ({}) results!", search.len()); - } + let mut game_path = None; - for path in &search { - print!("{path} - use this path? (y/n): "); - io::stdout().flush().unwrap(); - io::stdin().read_line(&mut input).unwrap(); + if search.len() == 1 { + println!("Found (1) result!"); + } else { + println!("Found ({}) results!", search.len()); + } - if input.trim().to_lowercase() == "y" { - game_path = Some(path); - break; + for path in &search { + print!("{path} - use this path? (y/n): "); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut input).unwrap(); + + if input.trim().to_lowercase() == "y" { + game_path = Some(path); + break; + } } - } - let input_dir = Path::new(game_path.expect(CANT_FIND_FOLDER)) - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join("TAGame/CookedPCConsole"); - - assert!( - input_dir.is_dir(), - "Couldn't find 'rocketleague/TAGame/CookedPCConsole' folder! Make sure you select the correct path to a Windows version of Rocket League." - ); - let input_dir = input_dir.to_string_lossy().to_string(); - - println!("Writing '{input_dir}' to 'assets.path'..."); - fs::write("assets.path", &input_dir).expect("Couldn't write to 'assets.path'!"); - - input_dir -} + let input_dir = Path::new(game_path.expect(CANT_FIND_FOLDER)) + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .join("TAGame/CookedPCConsole"); -fn get_input_dir() -> String { - let input_file = fs::read_to_string("assets.path").unwrap_or_else(|_| find_input_dir()); + assert!( + input_dir.is_dir(), + "Couldn't find 'rocketleague/TAGame/CookedPCConsole' folder! Make sure you select the correct path to a Windows version of Rocket League." + ); + let input_dir = input_dir.to_string_lossy().to_string(); - let Some(assets_dir) = input_file.lines().next() else { - panic!("Your 'assets.path' file is empty! Create the file with the path to your 'rocketleague/TAGame/CookedPCConsole' folder."); - }; + println!("Writing '{input_dir}' to 'assets.path'..."); + fs::write("assets.path", &input_dir).expect("Couldn't write to 'assets.path'!"); - let assets_path = Path::new(assets_dir); - if assets_path.is_dir() && assets_path.exists() { - assets_dir.to_string() - } else { - panic!("Couldn't find the directory specified in your 'assets.path'!"); + input_dir } -} -const UPK_FILES: [&str; 10] = [ - "Startup.upk", - "MENU_Main_p.upk", - "Stadium_P.upk", - "HoopsStadium_P.upk", - "Body_MuscleCar_SF.upk", - "Body_Darkcar_SF.upk", - "Body_CarCar_SF.upk", - "Body_Venom_SF.upk", - "Body_Force_SF.upk", - "Body_Vanquish_SF.upk", -]; - -fn has_existing_assets() -> io::Result { - //ensure all upk files are listen in ver_file - let ver_file = fs::read_to_string(OUT_DIR_VER)?; - let file_count = ver_file.lines().filter(|line| UPK_FILES.contains(line)).count(); + fn get_input_dir() -> String { + let input_file = fs::read_to_string("assets.path").unwrap_or_else(|_| find_input_dir()); - Ok(file_count == UPK_FILES.len()) -} + let Some(assets_dir) = input_file.lines().next() else { + panic!("Your 'assets.path' file is empty! Create the file with the path to your 'rocketleague/TAGame/CookedPCConsole' folder."); + }; -pub fn uncook() -> io::Result<()> { - if has_existing_assets().unwrap_or_default() { - info!("Found existing assets"); - return Ok(()); + let assets_path = Path::new(assets_dir); + if assets_path.is_dir() && assets_path.exists() { + assets_dir.to_string() + } else { + panic!("Couldn't find the directory specified in your 'assets.path'!"); + } } - // let upk_files = fs::read_dir(&input_dir)? - // .filter_map(|entry| { - // let entry = entry.unwrap(); - // let path = entry.path(); - // if path.is_file() && path.extension().unwrap_or_default() == "upk" { - // Some(path.file_name().unwrap().to_str().unwrap().to_string()) - // } else { - // None - // } - // }) - // .collect::>(); - - if !Path::new(UMODEL).exists() { - println!("Couldn't find UModel! Make sure it's in the same folder as the executable. Using default assets!"); - return Ok(()); - } + const UPK_FILES: [&str; 10] = [ + "Startup.upk", + "MENU_Main_p.upk", + "Stadium_P.upk", + "HoopsStadium_P.upk", + "Body_MuscleCar_SF.upk", + "Body_Darkcar_SF.upk", + "Body_CarCar_SF.upk", + "Body_Venom_SF.upk", + "Body_Force_SF.upk", + "Body_Vanquish_SF.upk", + ]; + + fn has_existing_assets() -> io::Result { + //ensure all upk files are listen in ver_file + let ver_file = fs::read_to_string(OUT_DIR_VER)?; + let file_count = ver_file.lines().filter(|line| UPK_FILES.contains(line)).count(); - let input_dir = get_input_dir(); - - info!("Uncooking assets from Rocket League..."); - - let num_files = UPK_FILES.len(); - // let num_files = upk_files.len(); - - for (i, file) in UPK_FILES.into_iter().enumerate() { - print!("Processing file {i}/{num_files} ({file})... \r"); - io::stdout().flush()?; - - // call umodel to uncook all the map files - let mut child = Command::new(UMODEL) - .args([ - format!("-path={input_dir}"), - format!("-out={OUT_DIR}"), - "-game=rocketleague".to_string(), - "-export".to_string(), - "-nooverwrite".to_string(), - "-nolightmap".to_string(), - "-uncook".to_string(), - "-uc".to_string(), - file.to_string(), - ]) - .stdout(Stdio::null()) - .spawn()?; - child.wait()?; + Ok(file_count == UPK_FILES.len()) } - // write each item in the list to "OUTDIR/files.txt" - fs::write(OUT_DIR_VER, UPK_FILES.join("\n"))?; + pub fn uncook() -> io::Result<()> { + if has_existing_assets().unwrap_or_default() { + info!("Found existing assets"); + return Ok(()); + } - println!("Done processing files "); + // let upk_files = fs::read_dir(&input_dir)? + // .filter_map(|entry| { + // let entry = entry.unwrap(); + // let path = entry.path(); + // if path.is_file() && path.extension().unwrap_or_default() == "upk" { + // Some(path.file_name().unwrap().to_str().unwrap().to_string()) + // } else { + // None + // } + // }) + // .collect::>(); + + if !Path::new(UMODEL).exists() { + println!("Couldn't find UModel! Make sure it's in the same folder as the executable. Using default assets!"); + return Ok(()); + } + + let input_dir = get_input_dir(); + + info!("Uncooking assets from Rocket League..."); + + let num_files = UPK_FILES.len(); + // let num_files = upk_files.len(); + + for (i, file) in UPK_FILES.into_iter().enumerate() { + print!("Processing file {i}/{num_files} ({file})... \r"); + io::stdout().flush()?; + + // call umodel to uncook all the map files + let mut child = Command::new(UMODEL) + .args([ + format!("-path={input_dir}"), + format!("-out={OUT_DIR}"), + "-game=rocketleague".to_string(), + "-export".to_string(), + "-nooverwrite".to_string(), + "-nolightmap".to_string(), + "-uncook".to_string(), + "-uc".to_string(), + file.to_string(), + ]) + .stdout(Stdio::null()) + .spawn()?; + child.wait()?; + } - Ok(()) + // write each item in the list to "OUTDIR/files.txt" + fs::write(OUT_DIR_VER, UPK_FILES.join("\n"))?; + + println!("Done processing files "); + + Ok(()) + } } diff --git a/src/main.rs b/src/main.rs index f14e075..034c5f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,12 +17,13 @@ use bevy::{ render::texture::{ImageAddressMode, ImageSamplerDescriptor}, window::PresentMode, }; -use settings::gui; +use settings::{cache_handler, gui}; use std::env; #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)] enum GameLoadState { #[default] + Cache, Connect, FieldExtra, Despawn, @@ -44,7 +45,8 @@ fn main() { // read the second argument and treat it as the port to bind the UDP socket to (u16) let secondary_port = args.next().and_then(|s| s.parse::().ok()).unwrap_or(45243); - assets::uncook().unwrap(); + #[cfg(debug_assertions)] + assets::umodel::uncook().unwrap(); App::new() .insert_resource(ServerPort { @@ -73,6 +75,7 @@ fn main() { debug: cfg!(feature = "debug"), ..default() }, + cache_handler::CachePlugin, camera::CameraPlugin, gui::DebugOverlayPlugin, mesh::FieldLoaderPlugin, diff --git a/src/mesh.rs b/src/mesh.rs index 58a0d30..6cb3248 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -1,7 +1,6 @@ use crate::{ assets::*, rocketsim::{GameMode, Team}, - settings::default_field::{get_hoops_floor, get_standard_floor, load_hoops, load_standard}, udp::{Ball, ToBevyVec, ToBevyVecFlat}, GameLoadState, }; @@ -14,14 +13,16 @@ use bevy::{ render::{ mesh::{self, VertexAttributeValues}, render_asset::RenderAssetUsages, + renderer::RenderDevice, }, time::Stopwatch, window::PrimaryWindow, }; use include_flate::flate; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, + fs::{create_dir_all, File}, io::{self, Read}, path::Path, rc::Rc, @@ -292,7 +293,7 @@ fn load_extra_field( let initial_ball_color = Color::srgb(0.3, 0.3, 0.3); let (ball_color, ball_texture) = match assets.get_load_state(&ball_assets.ball_diffuse) { - Some(LoadState::Failed(_)) | None => (Color::from(css::DARK_GRAY), None), + Some(LoadState::Failed(_)) => (Color::from(css::DARK_GRAY), None), _ => (Color::WHITE, Some(ball_assets.ball_diffuse.clone())), }; @@ -307,7 +308,7 @@ fn load_extra_field( }; let ball_mesh = match assets.get_load_state(&ball_assets.ball) { - Some(LoadState::Failed(_)) | None => meshes.add(Sphere::new(91.25)), + Some(LoadState::Failed(_)) => meshes.add(Sphere::new(91.25)), _ => ball_assets.ball.clone(), }; @@ -545,64 +546,15 @@ fn load_goals( } } -fn load_default_field( - commands: &mut Commands, - materials: &mut Assets, - meshes: &mut Assets, - game_mode: GameMode, -) { - let (field, floor) = match game_mode { - GameMode::Soccar => (meshes.add(load_standard()), meshes.add(get_standard_floor())), - GameMode::Hoops => (meshes.add(load_hoops()), meshes.add(get_hoops_floor())), - _ => return, - }; - - commands.spawn(( - PbrBundle { - mesh: field, - material: materials.add(StandardMaterial { - base_color: Color::srgba_u8(55, 30, 48, 200), - cull_mode: None, - double_sided: true, - ..default() - }), - ..default() - }, - #[cfg(debug_assertions)] - EntityName::from("default_field"), - RaycastPickable, - On::>::target_insert(HighlightedEntity), - On::>::target_remove::(), - StaticFieldEntity, - )); - - commands.spawn(( - PbrBundle { - mesh: floor, - material: materials.add(StandardMaterial { - base_color: Color::srgb_u8(45, 49, 66), - cull_mode: None, - double_sided: true, - ..default() - }), - ..default() - }, - #[cfg(debug_assertions)] - EntityName::from("default_floor"), - RaycastPickable, - On::>::target_insert(HighlightedEntity), - On::>::target_remove::(), - StaticFieldEntity, - )); -} - fn load_field( mut commands: Commands, mut materials: ResMut>, mut meshes: ResMut>, + mut images: ResMut>, mut state: ResMut>, mut large_boost_pad_loc_rots: ResMut, game_mode: Res, + render_device: Option>, asset_server: Res, ) { let layout: &str = match *game_mode { @@ -614,12 +566,6 @@ fn load_field( _ => &STADIUM_P_LAYOUT, }; - if !Path::new("assets").is_dir() { - load_default_field(&mut commands, &mut materials, &mut meshes, *game_mode); - state.set(GameLoadState::None); - return; - } - let (_pickup_boost, structures, the_world): (Section, Node, Node) = serde_json::from_str(layout).unwrap(); #[cfg(debug_assertions)] { @@ -652,6 +598,8 @@ fn load_field( &mut materials, &mut large_boost_pad_loc_rots, &mut commands, + &mut images, + render_device.as_deref(), ); continue; } @@ -664,6 +612,8 @@ fn load_field( &mut materials, &mut large_boost_pad_loc_rots, &mut commands, + &mut images, + render_device.as_deref(), ); } } @@ -681,6 +631,8 @@ fn process_info_node( materials: &mut Assets, large_boost_pad_loc_rots: &mut LargeBoostPadLocRots, commands: &mut Commands, + images: &mut Assets, + render_device: Option<&RenderDevice>, ) { if node.static_mesh.trim().is_empty() { return; @@ -727,7 +679,7 @@ fn process_info_node( mat.as_ref() }; - let material = get_material(mat_name, materials, asset_server, None, side); + let material = get_material(mat_name, materials, asset_server, None, side, images, render_device); let mut transform = node.get_transform(); @@ -775,7 +727,7 @@ pub enum MeshBuilderError { } /// A collection of inter-connected triangles. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct MeshBuilder { ids: Vec, verts: Vec, @@ -786,17 +738,27 @@ pub struct MeshBuilder { } impl MeshBuilder { + pub fn create_cache(&self, path: &Path) { + create_dir_all(path.parent().unwrap()).unwrap(); + let mut file = File::create(path).unwrap(); + bincode::serialize_into(&mut file, self).unwrap(); + } + + pub fn from_cache(reader: R) -> Self { + bincode::deserialize_from(reader).unwrap() + } + #[must_use] // Build the Bevy Mesh - pub fn build_meshes(self, scale: f32) -> Vec { + pub fn build_meshes(self) -> Vec { let num_materials = self.num_materials; if num_materials < 2 { - return vec![self.build_mesh(scale)]; + return vec![self.build_mesh()]; } let all_mat_ids = self.ids.iter().map(|&id| self.mat_ids[id]).collect::>(); - let initial_mesh = self.build_mesh(scale); + let initial_mesh = self.build_mesh(); let all_verts = initial_mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().as_float3().unwrap(); let VertexAttributeValues::Float32x2(all_uvs) = initial_mesh.attribute(Mesh::ATTRIBUTE_UV_0).unwrap() else { @@ -894,13 +856,13 @@ impl MeshBuilder { #[must_use] // Build the Bevy Mesh - pub fn build_mesh(self, scale: f32) -> Mesh { + pub fn build_mesh(self) -> Mesh { let mut mesh = Mesh::new(mesh::PrimitiveTopology::TriangleList, RenderAssetUsages::default()); let verts = self .verts .chunks_exact(3) - .map(|chunk| [chunk[0] * scale, chunk[1] * scale, chunk[2] * scale]) + .map(|chunk| [chunk[0], chunk[1], chunk[2]]) .collect::>(); let ids = self.ids.iter().map(|&id| verts[id]).collect::>(); diff --git a/src/settings/cache_handler.rs b/src/settings/cache_handler.rs new file mode 100644 index 0000000..cda9054 --- /dev/null +++ b/src/settings/cache_handler.rs @@ -0,0 +1,413 @@ +use crate::{assets::load_assets, mesh::MeshBuilder, GameLoadState}; +use bevy::{ + prelude::*, + render::{ + render_asset::RenderAssetUsages, + renderer::RenderDevice, + texture::{CompressedImageFormats, ImageSampler, ImageType}, + }, + utils::HashMap, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::{ + fs::{copy, create_dir_all, read_to_string, File}, + io::Read, + path::{Path, MAIN_SEPARATOR}, + sync::RwLock, +}; +use walkdir::WalkDir; + +static MESHES: RwLock>>>> = RwLock::new(Lazy::new(HashMap::new)); +static MESH_MATERIALS: RwLock>>> = RwLock::new(Lazy::new(HashMap::new)); +static TEXTURES: RwLock>>> = RwLock::new(Lazy::new(HashMap::new)); + +#[cfg(debug_assertions)] +mod cache { + use crate::GameLoadState; + use bevy::prelude::*; + + pub fn load_cache(mut state: ResMut>) { + state.set(GameLoadState::Connect); + } +} + +#[cfg(not(debug_assertions))] +mod cache { + use crate::GameLoadState; + use bevy::{prelude::*, render::renderer::RenderDevice}; + use include_flate::flate; + use std::io::Cursor; + use zip::ZipArchive; + + flate!(static CACHED_ASSETS: [u8] from "cache.zip"); + + pub fn load_cache( + mut state: ResMut>, + mut meshes: ResMut>, + mut images: ResMut>, + render_device: Option>, + ) { + let seeker = Cursor::new(&*CACHED_ASSETS); + let mut archive = ZipArchive::new(seeker).unwrap(); + + let mut mesh_cache = super::MESHES.write().unwrap(); + let mut material_cache = super::MESH_MATERIALS.write().unwrap(); + let mut texture_cache = super::TEXTURES.write().unwrap(); + + for i in 0..archive.len() { + let file = archive.by_index(i).unwrap(); + + if !file.is_file() { + continue; + } + + let file_name = file.enclosed_name().unwrap(); + let name = file_name.file_stem().unwrap().to_string_lossy().to_string(); + let parent = file_name.parent().unwrap().file_name().unwrap().to_string_lossy(); + + match parent.as_ref() { + "mesh" => { + let builder = super::MeshBuilder::from_cache(file); + let meshes = builder.build_meshes().into_iter().map(|mesh| meshes.add(mesh)).collect(); + mesh_cache.insert(name, meshes); + } + "textures" => { + let texture = super::read_tga(file, render_device.as_deref()); + texture_cache.insert(name, images.add(texture)); + } + "material" => { + let material = super::MeshMaterial::from_cache(file); + material_cache.insert(name, vec![material]); + } + _ => { + warn!("Unknown cache type {parent}"); + } + } + } + + state.set(GameLoadState::Connect); + } +} + +pub struct CachePlugin; + +impl Plugin for CachePlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + (cache::load_cache, load_assets) + .chain() + .run_if(in_state(GameLoadState::Cache)), + ); + } +} + +pub fn get_default_mesh_cache(path: &'static str, assets: &AssetServer, meshes: &mut Assets) -> Handle { + let name = path.split('/').last().unwrap().trim_end_matches(".pskx"); + + if let Some(meshes) = check_mesh_cache(name) { + return meshes[0].clone(); + } + + if !cfg!(debug_assertions) { + panic!("Failed to load mesh {name}"); + } + + let cache_path = format!("./cache/mesh/{name}.bin"); + if let Ok(mesh) = File::open(&cache_path) { + return meshes.add(MeshBuilder::from_cache(mesh).build_mesh()); + } + + warn!("Cache not found for mesh {name}"); + + let handle = assets.load(path); + MESHES.write().unwrap().insert(name.to_string(), vec![handle.clone()]); + + handle +} + +pub fn get_mesh_cache>( + cache_path: P, + asset_path: P, + name: &str, + meshes: &mut Assets, +) -> Option>> { + fn inner(cache_path: &Path, asset_path: &Path, name: &str, meshes: &mut Assets) -> Option>> { + let name = name.split('.').last().unwrap(); + if let Some(meshes) = check_mesh_cache(name) { + return Some(meshes); + } + + if !cfg!(debug_assertions) { + return None; + } + + if let Ok(file) = File::open(cache_path) { + let builder = MeshBuilder::from_cache(file); + return Some(insert_mesh_cache(name.to_string(), builder, meshes)); + } + + warn!("Cache not found for mesh {name}"); + + // read bytes from path + let Ok(mut file) = File::open(asset_path) else { + error!("Failed to open mesh {} for {name}", asset_path.display()); + return None; + }; + + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).ok()?; + + let builder = MeshBuilder::from_pskx(name, &bytes).ok()?; + builder.create_cache(cache_path); + Some(insert_mesh_cache(name.to_string(), builder, meshes)) + } + + inner(cache_path.as_ref(), asset_path.as_ref(), name, meshes) +} + +fn check_mesh_cache(name: &str) -> Option>> { + MESHES.read().ok()?.get(name).cloned() +} + +fn insert_mesh_cache(name: String, builder: MeshBuilder, meshes: &mut Assets) -> Vec> { + let meshes: Vec<_> = builder.build_meshes().into_iter().map(|mesh| meshes.add(mesh)).collect(); + + let Ok(mut map) = MESHES.write() else { + return meshes; + }; + + map.insert(name, meshes.clone()); + + meshes +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +pub enum CAlphaMode { + Opaque, + Mask(f32), + Blend, + Premultiplied, + AlphaToCoverage, + Add, + Multiply, +} + +impl From for AlphaMode { + fn from(mode: CAlphaMode) -> Self { + match mode { + CAlphaMode::Opaque => AlphaMode::Opaque, + CAlphaMode::Mask(threshold) => AlphaMode::Mask(threshold), + CAlphaMode::Blend => AlphaMode::Blend, + CAlphaMode::Premultiplied => AlphaMode::Premultiplied, + CAlphaMode::AlphaToCoverage => AlphaMode::AlphaToCoverage, + CAlphaMode::Add => AlphaMode::Add, + CAlphaMode::Multiply => AlphaMode::Multiply, + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct MeshMaterial { + pub diffuse: Option, + pub normal: Option, + pub other: Vec, + pub alpha_mode: Option, + pub mask_clip_value: f32, + pub double_sided: bool, +} + +impl MeshMaterial { + fn new(name: &str, pre_path: String) -> Option { + let path = format!("./assets/{pre_path}.mat"); + let Ok(mat_file) = read_to_string(&path) else { + error!("Failed to read {path} ({name})"); + return None; + }; + + let props = format!("./assets/{pre_path}.props.txt"); + let Ok(props_file) = read_to_string(props) else { + error!("Failed to read {path} ({name})"); + return None; + }; + + let mut diffuse = None; + let mut normal = None; + let mut other = Vec::new(); + + for line in mat_file.lines() { + // split at the first "=" + let mut split = line.split('='); + if let Some(key) = split.next() { + let Some(value) = split.next() else { + error!("No value for {key} in {path}"); + continue; + }; + + match key { + "Diffuse" => { + diffuse = Some(value.to_string()); + } + "Normal" => { + normal = Some(value.to_string()); + } + "Other[0]" => { + other.push(value.to_string()); + } + x => { + warn!("Unknown key {x} is {value} in {path} ({name})"); + } + } + } + } + + let mut alpha_mode = None; + let mut mask_clip_value = 0.333; + let mut double_sided = None; + + for line in props_file.lines() { + let mut split = line.split(" = "); + if let Some(key) = split.next() { + let Some(value) = split.next() else { + continue; + }; + + if key == "TwoSided" { + double_sided = Some(value == "true"); + } else if key == "BlendMode" { + alpha_mode = match value { + "BLEND_Opaque (0)" => Some(CAlphaMode::Opaque), + "BLEND_Masked (1)" => Some(CAlphaMode::Mask(mask_clip_value)), + "BLEND_Translucent (2)" => Some(CAlphaMode::Blend), + "BLEND_Additive (3)" => Some(CAlphaMode::Add), + _ => { + error!("Unknown blend mode {value} in {path} ({name})"); + None + } + }; + } else if key == "OpacityMaskClipValue" { + if let Ok(mask_value) = value.parse() { + mask_clip_value = mask_value; + + if let Some(CAlphaMode::Mask(_)) = alpha_mode { + alpha_mode = Some(CAlphaMode::Mask(mask_clip_value)); + } + } + } + } + } + + Some(Self { + diffuse, + normal, + other, + alpha_mode, + mask_clip_value, + double_sided: double_sided.unwrap_or_default(), + }) + } + + fn create_cache(&self, path: &Path) { + create_dir_all(path.parent().unwrap()).unwrap(); + let mut file = File::create(path).unwrap(); + bincode::serialize_into(&mut file, self).unwrap(); + } + + fn from_cache(file: R) -> Self { + bincode::deserialize_from(file).unwrap() + } +} + +pub fn get_material_cache>(cache_path: P, asset_path: P, name: &str) -> Option { + fn inner(cache_path: &Path, asset_path: &Path, name: &str) -> Option { + let name = name.split('.').last().unwrap(); + if let Some(materials) = MESH_MATERIALS.read().ok()?.get(name) { + return Some(materials[0].clone()); + } + + if !cfg!(debug_assertions) { + return None; + } + + if let Ok(file) = File::open(cache_path) { + return Some(MeshMaterial::from_cache(file)); + } + + warn!("Cache not found for material {name}"); + + let Some(material) = MeshMaterial::new(name, asset_path.to_string_lossy().to_string()) else { + error!("Failed to create material for {name}"); + return None; + }; + + material.create_cache(cache_path); + Some(material) + } + + inner(cache_path.as_ref(), asset_path.as_ref(), name) +} + +fn read_tga(mut reader: R, render_device: Option<&RenderDevice>) -> Image { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).unwrap(); + + let image_type = ImageType::Extension("tga"); + let supported_compressed_formats = match render_device { + Some(render_device) => CompressedImageFormats::from_features(render_device.features()), + None => CompressedImageFormats::NONE, + }; + + Image::from_buffer( + &bytes, + image_type, + supported_compressed_formats, + true, + ImageSampler::Default, + RenderAssetUsages::default(), + ) + .unwrap() +} + +pub fn get_texture_cache( + name: &str, + asset_server: &AssetServer, + images: &mut Assets, + render_device: Option<&RenderDevice>, +) -> Handle { + if let Some(texture) = TEXTURES.read().unwrap().get(name) { + return texture.clone(); + } + + if !cfg!(debug_assertions) { + panic!("Failed to load texture {name}"); + } + + let cache_path_name = format!("./cache/textures/{name}.tga"); + let cache_path = Path::new(&cache_path_name); + if cache_path.exists() { + let file = File::open(cache_path).unwrap(); + return images.add(read_tga(file, render_device)); + } + + warn!("Cache not found for texture {name}"); + + let mut assets_path = String::from("assets"); + assets_path.push(MAIN_SEPARATOR); + + let path = WalkDir::new("assets") + .into_iter() + .flatten() + .find(|x| x.file_name().to_string_lossy() == format!("{name}.tga")) + .unwrap() + .path() + .to_string_lossy() + .to_string() + .replace(&assets_path, ""); + + // copy file to cache_path + create_dir_all(cache_path.parent().unwrap()).unwrap(); + copy(format!("./assets/{path}"), cache_path).unwrap(); + + asset_server.load(path) +} diff --git a/src/settings/default_field.rs b/src/settings/default_field.rs deleted file mode 100644 index c2a5fae..0000000 --- a/src/settings/default_field.rs +++ /dev/null @@ -1,263 +0,0 @@ -use bevy::{ - math::{Mat3A, Vec3, Vec3A}, - render::{ - mesh::{Mesh, PrimitiveTopology}, - render_asset::RenderAssetUsages, - }, -}; -use byteorder::{LittleEndian, ReadBytesExt}; -use include_flate::flate; -use std::io::Cursor; - -fn extract_usize(cursor: &mut Cursor<&[u8]>) -> usize { - cursor - .read_u32::() - .unwrap_or_else(|e| unreachable!("Problem parsing ***_ids.dat: {e:?}")) as usize -} - -fn extract_f32(cursor: &mut Cursor<&[u8]>) -> f32 { - cursor - .read_f32::() - .unwrap_or_else(|e| unreachable!("Problem parsing ***_vertices.dat: {e:?}")) -} - -/// A collection of inter-connected triangles. -#[derive(Clone, Debug, Default)] -pub struct MeshBuilder { - ids: Vec, - vertices: Vec, -} - -impl MeshBuilder { - pub fn from_bytes(ids_dat: &[u8], vertices_dat: &[u8]) -> Self { - let ids = { - let ids_len = ids_dat.len() / 4; - let mut ids_cursor = Cursor::new(ids_dat); - - (0..ids_len).map(|_| extract_usize(&mut ids_cursor)).collect() - }; - - let vertices = { - let vertices_len = vertices_dat.len() / 4; - let mut vertices_cursor = Cursor::new(vertices_dat); - - (0..vertices_len / 3) - .map(|_| { - Vec3A::new( - extract_f32(&mut vertices_cursor), - extract_f32(&mut vertices_cursor), - extract_f32(&mut vertices_cursor), - ) - }) - .collect() - }; - - Self { ids, vertices } - } - - pub const fn new(ids: Vec, vertices: Vec) -> Self { - Self { ids, vertices } - } - - pub fn combine(other_meshes: [Self; N]) -> Self { - let (n_ids, n_verts) = other_meshes.iter().fold((0, 0), |(n_ids, n_verts), m| { - (n_ids + m.ids.len(), n_verts + m.vertices.len()) - }); - let mut id_offset = 0; - - let (ids, vertices) = other_meshes.into_iter().fold( - (Vec::with_capacity(n_ids), Vec::with_capacity(n_verts)), - |(mut ids, mut vertices), m| { - ids.extend(m.ids.iter().map(|id| id + id_offset)); - id_offset += m.vertices.len(); - vertices.extend(m.vertices.iter()); - (ids, vertices) - }, - ); - - Self { ids, vertices } - } - - pub fn transform(mut self, a: Mat3A) -> Self { - debug_assert_eq!(self.ids.len() % 3, 0); - - for vertex in self.vertices.iter_mut() { - *vertex = a * *vertex; - } - - // for transformations that flip things - // inside-out, change triangle winding - if a.determinant() < 0. { - for ids in self.ids.chunks_exact_mut(3) { - ids.swap(1, 2); - } - } - - self - } - - pub fn translate_y(mut self, p: f32) -> Self { - for vertex in self.vertices.iter_mut() { - vertex.y += p; - } - - self - } - - pub fn build(self) -> Mesh { - let positions = self - .ids - .iter() - .map(|&id| { - let vert = self.vertices[id]; - [vert.x, vert.z, -vert.y] - }) - .collect::>(); - let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()); - - mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); - mesh.compute_flat_normals(); - mesh - } -} - -macro_rules! include_mesh { - ($ids:literal, $verts:literal) => { - { - flate!(static IDS: [u8] from $ids); - flate!(static VERTS: [u8] from $verts); - MeshBuilder::from_bytes(&IDS, &VERTS) - } - }; -} - -#[must_use] -pub fn load_standard() -> Mesh { - let standard_corner = include_mesh!( - "default_assets/standard/standard_corner_ids.bin", - "default_assets/standard/standard_corner_vertices.bin" - ); - let standard_goal = include_mesh!( - "default_assets/standard/standard_goal_ids.bin", - "default_assets/standard/standard_goal_vertices.bin" - ); - let standard_ramps_0 = include_mesh!( - "default_assets/standard/standard_ramps_0_ids.bin", - "default_assets/standard/standard_ramps_0_vertices.bin" - ); - let standard_ramps_1 = include_mesh!( - "default_assets/standard/standard_ramps_1_ids.bin", - "default_assets/standard/standard_ramps_1_vertices.bin" - ); - - initialize_standard(standard_corner, standard_goal, standard_ramps_0, standard_ramps_1) -} - -#[must_use] -pub fn load_hoops() -> Mesh { - let hoops_corner = include_mesh!( - "default_assets/hoops/hoops_corner_ids.bin", - "default_assets/hoops/hoops_corner_vertices.bin" - ); - let hoops_net = include_mesh!( - "default_assets/hoops/hoops_net_ids.bin", - "default_assets/hoops/hoops_net_vertices.bin" - ); - let hoops_rim = include_mesh!( - "default_assets/hoops/hoops_rim_ids.bin", - "default_assets/hoops/hoops_rim_vertices.bin" - ); - let hoops_ramps_0 = include_mesh!( - "default_assets/hoops/hoops_ramps_0_ids.bin", - "default_assets/hoops/hoops_ramps_0_vertices.bin" - ); - let hoops_ramps_1 = include_mesh!( - "default_assets/hoops/hoops_ramps_1_ids.bin", - "default_assets/hoops/hoops_ramps_1_vertices.bin" - ); - - initialize_hoops(hoops_corner, hoops_net, hoops_rim, hoops_ramps_0, hoops_ramps_1) -} - -const FLIP_X: Mat3A = Mat3A::from_cols(Vec3A::NEG_X, Vec3A::Y, Vec3A::Z); -const FLIP_Y: Mat3A = Mat3A::from_cols(Vec3A::X, Vec3A::NEG_Y, Vec3A::Z); - -fn quad(p: Vec3A, e1: Vec3A, e2: Vec3A) -> MeshBuilder { - MeshBuilder::new( - vec![0, 1, 3, 1, 2, 3], - vec![p + e1 + e2, p - e1 + e2, p - e1 - e2, p + e1 - e2], - ) -} - -pub fn get_standard_floor() -> Mesh { - quad(Vec3A::ZERO, Vec3A::new(4096., 0., 0.), Vec3A::new(0., 5500., 0.)).build() -} - -pub fn initialize_standard( - standard_corner: MeshBuilder, - standard_goal: MeshBuilder, - standard_ramps_0: MeshBuilder, - standard_ramps_1: MeshBuilder, -) -> Mesh { - const Y_OFFSET: f32 = -5120.; - - let standard_goal_tf = standard_goal.translate_y(Y_OFFSET); - - let field_mesh = MeshBuilder::combine([ - standard_corner.clone().transform(FLIP_X), - standard_corner.clone().transform(FLIP_Y), - standard_corner.clone().transform(FLIP_X * FLIP_Y), - standard_corner, - standard_goal_tf.clone().transform(FLIP_X), - standard_goal_tf.clone().transform(FLIP_Y), - standard_goal_tf.clone().transform(FLIP_X * FLIP_Y), - standard_goal_tf, - standard_ramps_0.clone().transform(FLIP_X), - standard_ramps_0.clone().transform(FLIP_Y), - standard_ramps_0.clone().transform(FLIP_X * FLIP_Y), - standard_ramps_0, - standard_ramps_1.clone().transform(FLIP_X), - standard_ramps_1.clone().transform(FLIP_Y), - standard_ramps_1.clone().transform(FLIP_X * FLIP_Y), - standard_ramps_1, - ]); - - field_mesh.build() -} - -pub fn get_hoops_floor() -> Mesh { - quad(Vec3A::ZERO, Vec3A::new(2966., 0., 0.), Vec3A::new(0., 3581., 0.)).build() -} - -pub fn initialize_hoops( - hoops_corner: MeshBuilder, - hoops_net: MeshBuilder, - hoops_rim: MeshBuilder, - hoops_ramps_0: MeshBuilder, - hoops_ramps_1: MeshBuilder, -) -> Mesh { - const SCALE: f32 = 0.9; - const S: Mat3A = Mat3A::from_diagonal(Vec3::splat(SCALE)); - - const Y_OFFSET: f32 = 431.664; - - let hoops_net_tf = hoops_net.transform(S).translate_y(Y_OFFSET); - let hoops_rim_tf = hoops_rim.transform(S).translate_y(Y_OFFSET); - - let field_mesh = MeshBuilder::combine([ - hoops_corner.clone().transform(FLIP_X), - hoops_corner.clone().transform(FLIP_Y), - hoops_corner.clone().transform(FLIP_X * FLIP_Y), - hoops_corner, - hoops_net_tf.clone().transform(FLIP_Y), - hoops_net_tf, - hoops_rim_tf.clone().transform(FLIP_Y), - hoops_rim_tf, - hoops_ramps_0.clone().transform(FLIP_X), - hoops_ramps_0, - hoops_ramps_1.clone().transform(FLIP_Y), - hoops_ramps_1, - ]); - - field_mesh.build() -} diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 6f562d5..a4cfc18 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,4 +1,4 @@ -pub mod default_field; +pub mod cache_handler; pub mod gui; pub mod options; pub mod state_setting; diff --git a/src/settings/state_setting.rs b/src/settings/state_setting.rs index 7e95a7f..ff9440a 100644 --- a/src/settings/state_setting.rs +++ b/src/settings/state_setting.rs @@ -3,8 +3,7 @@ use crate::{ morton::Morton, udp::{Connection, GameStates, SendableUdp}, }; -use ahash::AHashMap; -use bevy::{math::Vec3A, prelude::*}; +use bevy::{math::Vec3A, prelude::*, utils::HashMap}; use bevy_egui::{egui, EguiContexts}; pub struct StateSettingInterface; @@ -66,12 +65,12 @@ fn set_bool_from_usize(b: &mut bool, i: usize) { struct UserSetPadState(u64); #[derive(Resource, PartialEq, Eq)] -pub struct EnablePadInfo(AHashMap); +pub struct EnablePadInfo(HashMap); impl Default for EnablePadInfo { #[inline] fn default() -> Self { - Self(AHashMap::with_capacity(48)) + Self(HashMap::with_capacity(48)) } } @@ -92,12 +91,12 @@ struct UserPadState { } #[derive(Resource)] -pub struct UserPadStates(AHashMap); +pub struct UserPadStates(HashMap); impl Default for UserPadStates { #[inline] fn default() -> Self { - Self(AHashMap::with_capacity(48)) + Self(HashMap::with_capacity(48)) } } @@ -338,12 +337,12 @@ fn update_ball_info( struct UserSetCarState(u32, SetCarStateAmount); #[derive(Resource, PartialEq, Eq)] -pub struct EnableCarInfo(AHashMap); +pub struct EnableCarInfo(HashMap); impl Default for EnableCarInfo { #[inline] fn default() -> Self { - Self(AHashMap::with_capacity(8)) + Self(HashMap::with_capacity(8)) } } @@ -370,12 +369,12 @@ struct UserCarState { } #[derive(Resource)] -pub struct UserCarStates(AHashMap); +pub struct UserCarStates(HashMap); impl Default for UserCarStates { #[inline] fn default() -> Self { - Self(AHashMap::with_capacity(8)) + Self(HashMap::with_capacity(8)) } } diff --git a/src/udp.rs b/src/udp.rs index df3489e..8ad4310 100644 --- a/src/udp.rs +++ b/src/udp.rs @@ -9,14 +9,15 @@ use crate::{ settings::options::{BallCam, CalcBallRot, GameSpeed, Options, PacketSmoothing, ShowTime}, GameLoadState, ServerPort, }; -use ahash::HashMap; use bevy::{ app::AppExit, asset::LoadState, math::{Mat3A, Vec3A}, pbr::{NotShadowCaster, NotShadowReceiver}, prelude::*, + render::renderer::RenderDevice, time::Stopwatch, + utils::HashMap, }; use crossbeam_channel::{Receiver, Sender}; use itertools::izip; @@ -204,6 +205,8 @@ fn spawn_car( materials: &mut Assets, asset_server: &AssetServer, car_wheel_mesh: &CarWheelMesh, + images: &mut Assets, + render_device: Option<&RenderDevice>, ) { let hitbox = car_info.config.hitbox_size.to_bevy(); let base_color = get_color_from_team(car_info.team); @@ -263,7 +266,15 @@ fn spawn_car( const CAR_BOOST_LENGTH: f32 = 50.; if cfg!(feature = "full_load") { - let mesh_materials = get_car_mesh_materials(mesh_id, materials, asset_server, base_color, car_info.team); + let mesh_materials = get_car_mesh_materials( + mesh_id, + materials, + asset_server, + base_color, + car_info.team, + images, + render_device, + ); mesh_info .into_iter() @@ -343,6 +354,8 @@ fn get_car_mesh_materials( asset_server: &AssetServer, base_color: Color, side: Team, + images: &mut Assets, + render_device: Option<&RenderDevice>, ) -> Vec> { let mesh_path = mesh_id.replace('.', "/"); let props = fs::read_to_string(format!("./assets/{mesh_path}.props.txt")).unwrap(); @@ -373,6 +386,8 @@ fn get_car_mesh_materials( asset_server, Some(base_color), Some(side), + images, + render_device, )); } mesh_materials @@ -829,6 +844,8 @@ fn pre_update_car( mut materials: ResMut>, mut user_cars: ResMut, car_wheel_mesh: Res, + mut images: ResMut>, + render_device: Option>, ) { correct_car_count( &cars, @@ -840,6 +857,8 @@ fn pre_update_car( &mut materials, &asset_server, &car_wheel_mesh, + &mut images, + render_device.as_deref(), ); } @@ -929,6 +948,8 @@ fn correct_car_count( materials: &mut Assets, asset_server: &AssetServer, car_wheel_mesh: &CarWheelMesh, + images: &mut Assets, + render_device: Option<&RenderDevice>, ) { // remove cars that no longer exist for (entity, car) in car_entities { @@ -945,7 +966,16 @@ fn correct_car_count( .filter(|car_info| !cars.iter().any(|id| id.0 == car_info.id)); for car_info in non_existant_cars { - spawn_car(car_info, &mut commands, meshes, materials, asset_server, car_wheel_mesh); + spawn_car( + car_info, + &mut commands, + meshes, + materials, + asset_server, + car_wheel_mesh, + images, + render_device, + ); } } @@ -971,12 +1001,12 @@ fn update_pads_count( let hitbox_material = materials.add(Color::NONE); let large_pad_mesh = match asset_server.get_load_state(&pad_glows.large) { - Some(LoadState::Failed(_)) | None => pad_glows.large_hitbox.clone(), + Some(LoadState::Failed(_)) => pad_glows.large_hitbox.clone(), _ => pad_glows.large.clone(), }; let small_pad_mesh = match asset_server.get_load_state(&pad_glows.small) { - Some(LoadState::Failed(_)) | None => pad_glows.small_hitbox.clone(), + Some(LoadState::Failed(_)) => pad_glows.small_hitbox.clone(), _ => pad_glows.small.clone(), };