diff --git a/Cargo.lock b/Cargo.lock index a56f128..1e88b66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,7 +210,7 @@ dependencies = [ "flate2", "itertools 0.13.0", "nom", - "strum", + "strum 0.26.3", "thiserror 1.0.69", ] @@ -1638,6 +1638,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" @@ -1892,7 +1901,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1984,6 +1993,15 @@ dependencies = [ "windows 0.54.0", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -2029,6 +2047,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctrlc" version = "3.5.0" @@ -2079,6 +2107,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "directories" version = "6.0.0" @@ -2106,6 +2144,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", +] + [[package]] name = "disqualified" version = "1.0.0" @@ -2186,6 +2235,15 @@ dependencies = [ "syn", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2330,6 +2388,15 @@ dependencies = [ "ttf-parser 0.20.0", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2337,7 +2404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -2351,12 +2418,27 @@ dependencies = [ "syn", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -2391,6 +2473,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 = "1.1.0" @@ -2693,6 +2785,124 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.8" @@ -2927,6 +3137,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "litrs" version = "0.4.2" @@ -2999,7 +3215,7 @@ dependencies = [ "bitflags 2.9.4", "block", "core-graphics-types 0.2.0", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", @@ -3075,6 +3291,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.8.0" @@ -3496,6 +3729,50 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl" +version = "0.10.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3663,6 +3940,10 @@ dependencies = [ "directories", "serde", "serde_json", + "strum 0.27.2", + "strum_macros 0.27.2", + "tungstenite", + "url", "uuid", ] @@ -3681,6 +3962,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "pp-rs" version = "0.2.1" @@ -3913,6 +4203,7 @@ checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" dependencies = [ "cpal", "lewton", + "symphonia", ] [[package]] @@ -4019,6 +4310,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4044,6 +4344,29 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.0" @@ -4099,6 +4422,17 @@ dependencies = [ "serde_core", ] +[[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" @@ -4233,9 +4567,15 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + [[package]] name = "strum_macros" version = "0.26.4" @@ -4249,6 +4589,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "svg_fmt" version = "0.4.5" @@ -4266,6 +4618,55 @@ dependencies = [ "zeno", ] +[[package]] +name = "symphonia" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + [[package]] name = "syn" version = "2.0.106" @@ -4277,6 +4678,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -4312,6 +4724,19 @@ dependencies = [ "slotmap", ] +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -4395,6 +4820,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.10.0" @@ -4542,6 +4977,24 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "sha1", + "thiserror 2.0.17", + "utf-8", +] + [[package]] name = "twox-hash" version = "2.1.2" @@ -4554,6 +5007,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "typewit" version = "1.14.2" @@ -4620,6 +5079,30 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "1.18.1" @@ -4649,6 +5132,12 @@ dependencies = [ "syn", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -5581,6 +6070,12 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "x11-dl" version = "2.21.0" @@ -5650,6 +6145,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeno" version = "0.3.3" @@ -5675,3 +6193,57 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 411a34b..2dedd2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,16 @@ codegen-units = 1 lto = "thin" [dependencies] -bevy = "0.17.2" +bevy = { version = "0.17.2", features = ["mp3"] } bevy_aseprite_ultra = "0.7.0" bevy_dev_tools = "0.17.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" directories = "6.0" +tungstenite = { version = "0.28.0", features = ["native-tls"] } +url = "2.5.7" +strum = "0.27.2" +strum_macros = "0.27.2" [dev-dependencies] uuid = "1.18.1" diff --git a/README.md b/README.md index b77f1be..3a50b5b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ cargo run 1. [Install Rust](https://rust-lang.org/tools/install/) 2. [Install Bevy OS depedencies](https://bevy.org/learn/quick-start/getting-started/setup/#installing-os-dependencies) +3. Install `openssl` ```sh git clone https://gitlab.uni-ulm.de/softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik pomomon-garden diff --git a/assets/achievement.aseprite b/assets/achievement.aseprite new file mode 100644 index 0000000..2d1c413 Binary files /dev/null and b/assets/achievement.aseprite differ diff --git a/assets/config.json b/assets/config.json index 56bc933..0e6ba58 100644 --- a/assets/config.json +++ b/assets/config.json @@ -1,9 +1,9 @@ { - "grid_width": 12, - "grid_height": 4, + "grid_width": 15, + "grid_height": 5, "pom_speed": 1.5, "shovel_base_price": 10, - "shovel_rate": 0.5, + "shovel_rate": 0.2, "berry_seeds": [ { "name": "Normale Samen", @@ -26,5 +26,7 @@ "slice": "Seed3", "growth_stages": 6 } - ] -} \ No newline at end of file + ], + "wonder_event_url": "wss://pomomon.farm/ws", + "berries_per_focus_minute": 1 +} diff --git a/assets/shovel.aseprite b/assets/shovel.aseprite new file mode 100644 index 0000000..9c21f81 Binary files /dev/null and b/assets/shovel.aseprite differ diff --git a/assets/sounds/beep.mp3 b/assets/sounds/beep.mp3 new file mode 100644 index 0000000..0d1f255 Binary files /dev/null and b/assets/sounds/beep.mp3 differ diff --git a/flake.nix b/flake.nix index 59059c3..3469861 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,8 @@ libxkbcommon # linker lld + + openssl ]; runtimeLibs = pkgs.lib.makeLibraryPath bevyDeps; diff --git a/src/features/achievement/components.rs b/src/features/achievement/components.rs new file mode 100644 index 0000000..fef5673 --- /dev/null +++ b/src/features/achievement/components.rs @@ -0,0 +1,85 @@ +use crate::{features::phase::components::SessionTracker, prelude::*}; +use std::collections::HashMap; +use strum_macros::EnumIter; + +/// Represents an unlockable achievement. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, EnumIter)] +pub enum AchievementId { + // Berry achievements + FirstSteps, + MasterFarmer, + BerryTycoon, + // Focus achievements, + GettingStarted, + FocusMaster, + ZenMaster, + // Withered achievements + Negligent, + CompostKing, +} + +impl AchievementId { + /// Title to be displayed ingame + pub fn title(&self) -> String { + match self { + AchievementId::FirstSteps => "Erste Schritte", + AchievementId::MasterFarmer => "Meisterbauer", + AchievementId::BerryTycoon => "Beeren-Tycoon", + AchievementId::GettingStarted => "Aller Anfang", + AchievementId::FocusMaster => "Fokus-Meister", + AchievementId::ZenMaster => "Zen-Meister", + AchievementId::Negligent => "Nachlässig", + AchievementId::CompostKing => "Kompost-König", + } + .into() + } + + /// Description to be displayed ingame + pub fn description(&self) -> String { + match self { + AchievementId::FirstSteps => "Verdiene eine Beere.", + AchievementId::MasterFarmer => "Verdiene 100 Beeren.", + AchievementId::BerryTycoon => "Verdiene 1.000 Beeren.", + AchievementId::GettingStarted => "Schließe deine erste Fokus-Phase ab.", + AchievementId::FocusMaster => "Schließe 10 Fokus-Phasen ab.", + AchievementId::ZenMaster => "Schließe 50 Fokus-Phasen ab.", + AchievementId::Negligent => "Lasse eine Pflanze verdorren.", + AchievementId::CompostKing => "Lasse 10 Pflanzen verdorren.", + } + .into() + } + + /// Label to be displayed ingame (Title: Description) + pub fn label(&self) -> String { + format!("{}: {}", self.title(), self.description()) + } + + /// Checks if an achievement's conditions are met + pub fn conditions_met(&self, tracker: &SessionTracker) -> bool { + match self { + AchievementId::FirstSteps => tracker.total_berries_earned >= 1, + AchievementId::MasterFarmer => tracker.total_berries_earned >= 100, + AchievementId::BerryTycoon => tracker.total_berries_earned >= 1000, + AchievementId::GettingStarted => tracker.completed_focus_phases >= 1, + AchievementId::FocusMaster => tracker.completed_focus_phases >= 10, + AchievementId::ZenMaster => tracker.completed_focus_phases >= 50, + AchievementId::Negligent => tracker.total_plants_withered >= 1, + AchievementId::CompostKing => tracker.total_plants_withered >= 10, + } + } +} + +#[derive(Resource, Default, Debug, Serialize, Deserialize, Clone)] +pub struct AchievementProgress { + pub unlocked: HashMap, +} + +impl AchievementProgress { + pub fn is_unlocked(&self, id: &AchievementId) -> bool { + *self.unlocked.get(id).unwrap_or(&false) + } + + pub fn unlock(&mut self, id: AchievementId) { + self.unlocked.insert(id, true); + } +} diff --git a/src/features/achievement/mod.rs b/src/features/achievement/mod.rs new file mode 100644 index 0000000..3d8d7a0 --- /dev/null +++ b/src/features/achievement/mod.rs @@ -0,0 +1,33 @@ +use crate::features::notification::components::Notifications; +use crate::features::phase::components::SessionTracker; +use crate::prelude::*; +use components::{AchievementId, AchievementProgress}; +use strum::IntoEnumIterator; + +pub mod components; +pub mod ui; + +pub struct AchievementPlugin; + +impl Plugin for AchievementPlugin { + fn build(&self, app: &mut App) { + app.init_resource::(); + app.add_systems( + Update, + check_achievements.run_if(in_state(AppState::GameScreen)), + ); + } +} + +pub fn check_achievements( + tracker: Res, + mut progress: ResMut, + mut notifications: ResMut, +) { + for achievement in AchievementId::iter() { + if !progress.is_unlocked(&achievement) && achievement.conditions_met(&tracker) { + progress.unlock(achievement.clone()); + notifications.info(Some("Erfolg freigeschaltet!"), achievement.label()); + } + } +} diff --git a/src/features/achievement/ui.rs b/src/features/achievement/ui.rs new file mode 100644 index 0000000..8dbe75c --- /dev/null +++ b/src/features/achievement/ui.rs @@ -0,0 +1,90 @@ +use crate::features::achievement::components::{AchievementId, AchievementProgress}; +use crate::features::ui::ui::popups::spawn_popup; +use crate::prelude::*; +use strum::IntoEnumIterator; + +#[derive(Component)] +pub enum AchievementRootMarker { + Menu, +} + +pub fn open_achievements_menu(commands: &mut Commands, progress: &AchievementProgress) { + spawn_popup( + commands, + AchievementRootMarker::Menu, + "Erfolge", + Node { + width: px(700), + height: px(500), + ..default() + }, + |parent| { + // Scrollable Content + parent + .spawn((Node { + width: percent(100), + height: percent(100), + flex_direction: FlexDirection::Column, + overflow: Overflow::scroll_y(), + padding: UiRect::all(px(10)), + row_gap: px(10), + ..default() + },)) + .with_children(|list| { + for id in AchievementId::iter() { + let unlocked = progress.is_unlocked(&id); + let color = if unlocked { + Color::WHITE + } else { + Color::srgb(0.5, 0.5, 0.5) + }; + + let bg_color = if unlocked { + Color::srgb(0.2, 0.3, 0.2) // Dark Greenish for unlocked + } else { + Color::srgb(0.1, 0.1, 0.1) // Dark Grey for locked + }; + + list.spawn(( + Node { + width: percent(100), + padding: UiRect::all(px(10)), + flex_direction: FlexDirection::Column, + row_gap: px(5), + border: UiRect::all(px(2)), + ..default() + }, + BackgroundColor(bg_color), + BorderColor::all(if unlocked { + Color::srgb(0.4, 0.8, 0.4) + } else { + Color::BLACK + }), + BorderRadius::all(px(5)), + )) + .with_children(|item| { + // Title + item.spawn(text(id.title(), 20.0, color)); + // Description + item.spawn(text(id.description(), 16.0, color)); + // Status Text + let status = if unlocked { + "Freigeschaltet" + } else { + "Gesperrt" + }; + item.spawn(text( + status, + 12.0, + if unlocked { + Color::srgb(0.6, 1.0, 0.6) + } else { + Color::srgb(0.7, 0.3, 0.3) + }, + )); + }); + } + }); + }, + ); +} diff --git a/src/features/config/components.rs b/src/features/config/components.rs index 6533682..f1c5076 100644 --- a/src/features/config/components.rs +++ b/src/features/config/components.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use std::fs::File; use std::io::BufReader; +/// Global configuration loaded from file, containing balancing numbers and paths. #[derive(Resource, Deserialize, Debug)] pub struct GameConfig { pub grid_width: u32, @@ -10,8 +11,11 @@ pub struct GameConfig { pub shovel_base_price: u32, pub shovel_rate: f32, pub berry_seeds: Vec, + pub wonder_event_url: String, + pub berries_per_focus_minute: u32, } +/// Configuration for a specific type of seed. #[derive(Deserialize, Debug, Clone)] pub struct BerrySeedConfig { pub name: String, @@ -52,15 +56,19 @@ impl Default for GameConfig { growth_stages: 6, }, ], + wonder_event_url: "wss://pomomon.farm/ws".into(), + berries_per_focus_minute: 1, } } } impl GameConfig { + /// Reads `config.json` from assets. pub fn read_config() -> Option { Self::read_from_path(std::path::Path::new("assets/config.json")) } + /// Reads configuration from a specific path. pub fn read_from_path(path: &std::path::Path) -> Option { let file = File::open(path).ok()?; let reader = BufReader::new(file); diff --git a/src/features/core/mod.rs b/src/features/core/mod.rs index bc0e573..cc979a0 100644 --- a/src/features/core/mod.rs +++ b/src/features/core/mod.rs @@ -2,6 +2,7 @@ use crate::prelude::*; pub mod states; +/// Handles core engine setup like camera and initial state. pub struct CorePlugin; impl Plugin for CorePlugin { @@ -11,6 +12,7 @@ impl Plugin for CorePlugin { } } +/// Spawns the main 2D camera. fn setup_camera(mut commands: Commands) { commands.spawn(Camera2d::default()); } diff --git a/src/features/core/states.rs b/src/features/core/states.rs index e14ed92..8ae853b 100644 --- a/src/features/core/states.rs +++ b/src/features/core/states.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +/// Global states of the application. #[derive(States, Clone, PartialEq, Eq, Debug, Hash, Default)] pub enum AppState { #[default] diff --git a/src/features/game_screen/mod.rs b/src/features/game_screen/mod.rs index 2a74417..2d865ce 100644 --- a/src/features/game_screen/mod.rs +++ b/src/features/game_screen/mod.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +/// Plugin for the main game screen, managing the game loop and environment. pub struct GameScreenPlugin; impl Plugin for GameScreenPlugin { @@ -9,10 +10,12 @@ impl Plugin for GameScreenPlugin { } } +/// Sets up the game screen environment (e.g., background color). fn setup(mut clear_color: ResMut) { *clear_color = ClearColor(Color::srgb(0.294, 0.412, 0.184)); } +/// Cleans up resources when exiting the game screen. fn cleanup(mut clear_color: ResMut) { *clear_color = ClearColor(Color::srgb(0.2, 0.2, 0.2)); } diff --git a/src/features/grid/components.rs b/src/features/grid/components.rs index 571e63f..89c95a5 100644 --- a/src/features/grid/components.rs +++ b/src/features/grid/components.rs @@ -1,19 +1,23 @@ use super::errors::GridError; use crate::prelude::*; +/// Component representing a single tile on the grid. #[derive(Component)] pub struct Tile { pub x: u32, pub y: u32, } +/// Visual marker component for the crop on a tile. #[derive(Component)] pub struct CropVisual; +/// Visual marker component for the water on a tile. #[derive(Component)] pub struct WaterVisual; -#[derive(Component, Default, Serialize, Deserialize, Clone, Debug)] +/// The logical state of a tile. +#[derive(Component, Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum TileState { #[default] Unclaimed, @@ -38,6 +42,7 @@ impl TileState { } } +/// Resource containing grid dimensions and tile entities. #[derive(Resource)] pub struct Grid { pub width: u32, @@ -46,6 +51,7 @@ pub struct Grid { } impl Grid { + /// Returns the entity of the tile at the given position. pub fn get_tile(&self, pos: (u32, u32)) -> Result { if pos.0 >= self.width || pos.1 >= self.height { return Err(GridError::OutOfBounds { @@ -56,6 +62,7 @@ impl Grid { Ok(self.tiles[pos.0 as usize][pos.1 as usize]) } + /// Modifies the state of a tile using a mapping function. pub fn map_tile_state( &self, pos: (u32, u32), @@ -74,4 +81,19 @@ impl Grid { *tile_state = mapper(&*tile_state); Ok(()) } -} \ No newline at end of file + + /// Counts the number of tiles that are not unclaimed. + pub fn count_claimed_tiles(&self, tile_query: &Query<&TileState>) -> u32 { + self.tiles + .iter() + .flatten() + .filter(|&entity| { + if let Ok(state) = tile_query.get(*entity) { + !matches!(state, TileState::Unclaimed) + } else { + false + } + }) + .count() as u32 + } +} diff --git a/src/features/grid/consts.rs b/src/features/grid/consts.rs index bc45548..d1f91b7 100644 --- a/src/features/grid/consts.rs +++ b/src/features/grid/consts.rs @@ -1 +1,2 @@ +/// The pixel size of a tile (width and height). pub const TILE_SIZE: f32 = 32.0; diff --git a/src/features/grid/errors.rs b/src/features/grid/errors.rs index fc77c3c..ee80a0f 100644 --- a/src/features/grid/errors.rs +++ b/src/features/grid/errors.rs @@ -1,5 +1,6 @@ use std::{error::Error, fmt}; +/// Errors related to grid operations. #[derive(Debug, Clone, PartialEq, Eq)] pub enum GridError { OutOfBounds { x: i32, y: i32 }, diff --git a/src/features/grid/mod.rs b/src/features/grid/mod.rs index d2a064f..02e6eee 100644 --- a/src/features/grid/mod.rs +++ b/src/features/grid/mod.rs @@ -1,11 +1,13 @@ use crate::prelude::*; use components::{CropVisual, WaterVisual}; +use std::collections::HashSet; pub mod components; pub mod consts; pub mod errors; pub mod utils; +/// Manages the game grid, including tiles, visuals, and updates. pub struct GridPlugin; impl Plugin for GridPlugin { @@ -17,6 +19,7 @@ impl Plugin for GridPlugin { } } +/// Initializes the grid and spawns tile entities. fn setup(mut commands: Commands, asset_server: Res, config: Res) { let grid_width = config.grid_width; let grid_height = config.grid_height; @@ -26,13 +29,28 @@ fn setup(mut commands: Commands, asset_server: Res, config: Res "Unclaimed", + TileState::Empty => "Empty", + _ => unreachable!(), + } + .into(), + aseprite: asset_server.load(match initial_state { + TileState::Unclaimed => "tiles/tile-unclaimed.aseprite", + TileState::Empty => "tiles/tile-empty.aseprite", + _ => unreachable!(), + }), }, Sprite::default(), Transform::from_translation(grid_to_world_coords( @@ -80,6 +98,7 @@ fn setup(mut commands: Commands, asset_server: Res, config: Res>) { for tile_entity in tile_query.iter() { commands.entity(tile_entity).despawn(); @@ -87,8 +106,12 @@ fn cleanup(mut commands: Commands, tile_query: Query>) { commands.remove_resource::(); } +/// Updates tile visuals based on their state (e.g., crop growth, highlighting). fn update_tiles( - mut query: Query<(&TileState, &mut AseSlice, &Children), (With, Without)>, + mut query: Query< + (&TileState, &mut AseSlice, &Children, &Tile), + (With, Without), + >, mut crop_query: Query< (&mut Visibility, &mut Transform, &mut AseSlice), (With, Without, Without), @@ -99,8 +122,27 @@ fn update_tiles( >, asset_server: Res, game_config: Res, + inventory: Res, + item_stacks: Query<&ItemStack>, + grid: Res, + mut sprite_query: Query<&mut Sprite, With>, ) { - for (state, mut slice, children) in &mut query { + let has_shovel = inventory.has_item_type(&item_stacks, ItemType::Shovel); + + let owned_tiles: HashSet<(u32, u32)> = query + .iter() + .filter_map(|(state, _, _, tile)| { + if !matches!(state, TileState::Unclaimed) { + Some((tile.x, tile.y)) + } else { + None + } + }) + .collect(); + + for (state, mut slice, children, tile) in &mut query { + let entity = grid.get_tile((tile.x, tile.y)).unwrap(); // Get entity for sprite query + slice.name = match state { TileState::Unclaimed => "Unclaimed", TileState::Empty => "Empty", @@ -133,6 +175,34 @@ fn update_tiles( _ => Vec3::ONE, }; + let mut is_highlighted = false; + if has_shovel && matches!(state, TileState::Unclaimed) { + // Check if not on edge + if tile.x > 0 && tile.x < grid.width - 1 && tile.y > 0 && tile.y < grid.height - 1 { + // Check neighbors + let neighbors = [ + (tile.x + 1, tile.y), + (tile.x.saturating_sub(1), tile.y), + (tile.x, tile.y + 1), + (tile.x, tile.y.saturating_sub(1)), + ]; + for n in neighbors.iter() { + if owned_tiles.contains(n) { + is_highlighted = true; + break; + } + } + } + } + + if let Ok(mut sprite) = sprite_query.get_mut(entity) { + if is_highlighted { + sprite.color = Color::srgb(0.3, 1.0, 0.3); // Green tint + } else { + sprite.color = Color::WHITE; + } + } + for child in children.iter() { if let Ok((mut visibility, mut transform, mut sprite)) = crop_query.get_mut(child) { *visibility = match state { diff --git a/src/features/grid/utils.rs b/src/features/grid/utils.rs index 95c722e..fb816ef 100644 --- a/src/features/grid/utils.rs +++ b/src/features/grid/utils.rs @@ -1,14 +1,17 @@ use super::errors::GridError; use crate::prelude::*; +/// Calculates the starting X coordinate for centering the grid. pub fn grid_start_x(grid_width: u32) -> f32 { -(grid_width as f32 * TILE_SIZE) / 2.0 + TILE_SIZE / 2.0 } +/// Calculates the starting Y coordinate for centering the grid. pub fn grid_start_y(grid_height: u32) -> f32 { -(grid_height as f32 * TILE_SIZE) / 2.0 + TILE_SIZE / 2.0 } +/// Converts world coordinates to grid coordinates. pub fn world_to_grid_coords( world_pos: Vec3, grid_width: u32, @@ -30,6 +33,7 @@ pub fn world_to_grid_coords( Ok((x as u32, y as u32)) } +/// Converts grid coordinates to world coordinates. pub fn grid_to_world_coords( grid_x: u32, grid_y: u32, diff --git a/src/features/hud/components.rs b/src/features/hud/components.rs index 6d93783..449cb5a 100644 --- a/src/features/hud/components.rs +++ b/src/features/hud/components.rs @@ -1,29 +1,34 @@ use crate::{features::phase::components::TimerSettings, prelude::*}; +/// Markers for root UI nodes. #[derive(Component)] pub enum RootMarker { Status, Settings, + ShovelOverlay, } +/// Markers for text components in the HUD. #[derive(Component)] pub enum TextType { Phase, Timer, } +/// Markers for buttons in the HUD and settings. #[derive(Component)] pub enum ButtonType { SettingsOpen, - SettingsClose, SettingsExit, SettingsSave, + SettingsAchievements, SettingsTimerChange { input: SettingsTimerInput, amount: i32, }, } +/// Types of timers available in the game. #[derive(Clone)] pub enum TimerType { Focus, @@ -32,6 +37,7 @@ pub enum TimerType { } impl TimerSettings { + /// Changes the duration of a specific timer. pub fn change(&mut self, timer_type: &TimerType, amount: i32) { match timer_type { TimerType::Focus => { @@ -59,6 +65,7 @@ impl TimerSettings { } } +/// Input types for adjusting timer settings. #[derive(Component, Clone)] pub enum SettingsTimerInput { Minutes(TimerType), diff --git a/src/features/hud/mod.rs b/src/features/hud/mod.rs index 875c501..27fa6fa 100644 --- a/src/features/hud/mod.rs +++ b/src/features/hud/mod.rs @@ -1,3 +1,4 @@ +use crate::features::achievement::{components::AchievementProgress, ui::open_achievements_menu}; use crate::features::phase::components::TimerSettings; use crate::features::savegame::messages::SavegameDumpMessage; use crate::features::{inventory, shop}; @@ -8,6 +9,7 @@ use ui::*; pub mod components; pub mod ui; +/// Plugin for the Head-Up Display (HUD) containing status bars and buttons. pub struct HudPlugin; impl Plugin for HudPlugin { @@ -16,12 +18,19 @@ impl Plugin for HudPlugin { app.add_systems(OnExit(AppState::GameScreen), cleanup); app.add_systems( Update, - (update_status, buttons, update_timer_settings).run_if(in_state(AppState::GameScreen)), + ( + update_status, + buttons, + update_timer_settings, + update_shovel_overlay_visibility, + ) + .run_if(in_state(AppState::GameScreen)), ); } } -fn setup(mut commands: Commands) { +/// Initializes the HUD UI. +fn setup(mut commands: Commands, game_config: Res, asset_server: Res) { commands.spawn(( RootMarker::Status, Node { @@ -44,34 +53,83 @@ fn setup(mut commands: Commands) { button( shop::components::ButtonType::ShopOpen, ButtonVariant::Secondary, - Node { - padding: UiRect::all(px(10)), - ..default() - }, + Node::from_padding(UiRect::all(px(10))), |color| text("Shop [P]", 16.0, color) ), button( inventory::components::ButtonType::InventoryOpen, ButtonVariant::Secondary, - Node { - padding: UiRect::all(px(10)), - ..default() - }, - |color| text("Inventar", 16.0, color) + Node::from_padding(UiRect::all(px(10))), + |color| text("Inventar [I]", 16.0, color) ), button( ButtonType::SettingsOpen, ButtonVariant::Secondary, - Node { - padding: UiRect::all(px(10)), - ..default() - }, - |color| text("Einstellungen", 16.0, color) + Node::from_padding(UiRect::all(px(10))), + |color| text("Einstellungen [Esc]", 16.0, color) ) ], )); + + // Shovel Overlay + commands.spawn(( + RootMarker::ShovelOverlay, + Node { + position_type: PositionType::Absolute, + top: px(20), + left: px(0), + right: px(0), + width: percent(100), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + flex_direction: FlexDirection::Column, + row_gap: px(5), + ..default() + }, + Visibility::Hidden, + children![( + Node { + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + padding: UiRect::all(px(10)), + row_gap: px(5), + ..default() + }, + BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.7)), + BorderRadius::all(px(10)), + children![ + ( + Node { + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + column_gap: px(10), + ..default() + }, + children![ + ( + Node { + width: px(32), + height: px(32), + ..default() + }, + inventory::components::ItemType::Shovel + .get_sprite(&asset_server, &game_config), + ImageNode::default() + ), + text("Schaufel-Modus", 20.0, Color::WHITE) + ] + ), + text( + "Klicke auf ein freies Feld, um es freizuschalten.", + 14.0, + Color::WHITE + ) + ] + )], + )); } +/// Updates the status text (phase and timer). fn update_status(phase_res: Res, mut text_query: Query<(&mut Text, &TextType)>) { if !phase_res.is_changed() { return; @@ -86,28 +144,29 @@ fn update_status(phase_res: Res, mut text_query: Query<(&mut Text, } } +/// Handles HUD button interactions. fn buttons( mut commands: Commands, mut interaction_query: Query<(&Interaction, &ButtonType), (Changed, With