diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..bc0b7619 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-pc-windows-msvc] +linker = "rust-lld" + +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "linker=clang", "-C", "link-arg=-fuse-ld=mold"] diff --git a/.github/workflows/build-steam.yml b/.github/workflows/build-steam.yml index 59c1233f..5807627c 100644 --- a/.github/workflows/build-steam.yml +++ b/.github/workflows/build-steam.yml @@ -16,14 +16,14 @@ jobs: - name: Install libraries run: | sudo apt update - sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev -y + sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev clang lld -y - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build luminol (Release) - run: cargo build --features steamworks --release + run: cargo build --features steamworks --release --jobs 1 - name: Setup artifact run: | mkdir -p ${{ github.workspace }}/artifact @@ -44,11 +44,11 @@ jobs: submodules: true - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build luminol (Release) - run: cargo build --features steamworks --release + run: cargo build --features steamworks --release --jobs 1 - name: Setup artifact run: | mkdir -p ${{ github.workspace }}/artifact @@ -69,11 +69,11 @@ jobs: submodules: true - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build luminol (Release) - run: cargo build --features steamworks --release + run: cargo build --features steamworks --release --jobs 1 - name: Setup artifact run: | mkdir -p ${{ github.workspace }}/artifact diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 302cbf4b..be063897 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,14 +15,14 @@ jobs: - name: Install libraries run: | sudo apt update - sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev -y + sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev clang mold -y - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build luminol (Release) - run: cargo build --release + run: cargo build --release --jobs 1 - name: Setup artifact run: | mkdir -p ${{ github.workspace }}/artifact @@ -42,11 +42,11 @@ jobs: submodules: true - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build luminol (Release) - run: cargo build --release + run: cargo build --release --jobs 1 - name: Setup artifact run: | mkdir -p ${{ github.workspace }}/artifact @@ -66,11 +66,11 @@ jobs: submodules: true - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build luminol (Release) - run: cargo build --release + run: cargo build --release --jobs 1 - name: Setup artifact run: | mkdir -p ${{ github.workspace }}/artifact diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 122ae13d..12a6c753 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -13,13 +13,13 @@ jobs: - name: Install libraries run: | sudo apt update - sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev + sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev clang mold -y - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - - run: cargo check --all-features + - run: cargo check --all-features --jobs 1 test: name: Test Suite @@ -30,13 +30,13 @@ jobs: submodules: true - run: | sudo apt update - sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libgtk-3-dev libasound2-dev -y + sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev clang mold -y - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 - name: Rust Cache uses: Swatinem/rust-cache@v2 - - run: cargo test --lib + - run: cargo test --lib --jobs 1 fmt: name: Rustfmt @@ -48,10 +48,10 @@ jobs: - name: Install libraries run: | sudo apt update - sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev -y + sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev clang mold -y - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 components: rustfmt - name: Rust Cache uses: Swatinem/rust-cache@v2 @@ -67,11 +67,11 @@ jobs: - name: Install libraries run: | sudo apt update - sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev -y + sudo apt install libgtk-3-dev libatk1.0-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libasound2-dev clang mold -y - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2023-04-21 + toolchain: nightly-2023-09-12 components: clippy - name: Rust Cache uses: Swatinem/rust-cache@v2 - - run: cargo clippy -- # -D warnings \ No newline at end of file + - run: cargo clippy --jobs 1 -- # -D warnings \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1af4a611..ba1c26ee 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ git-conventional-commits.yaml # Steamworks common folder steamworks/* # Allow the redistributable, though -!steamworks/redistributable_bin \ No newline at end of file +!steamworks/redistributable_bin +#! renderdoc +captures/ diff --git a/.gitmodules b/.gitmodules index 696c8716..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "alox-48"] - path = alox-48 - url = https://github.com/Speak2Erase/alox-48 diff --git a/.vscode/settings.json b/.vscode/settings.json index 7279758f..f2468ad8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,13 +19,17 @@ "playtest", "wasm", "filesystem", - "image cache" + "image cache", + "tilemap", + "config", + "audio" ], "rust-analyzer.showUnlinkedFileNotification": false, "rust-analyzer.cargo.extraEnv": { "CARGO_PROFILE_RUST_ANALYZER_INHERITS": "dev" }, - "rust-analyzer.cargo.extraArgs": [ + "rust-analyzer.cargo.extraArgs": [], + "rust-analyzer.check.extraArgs": [ "--profile", "rust-analyzer" ], diff --git a/Cargo.lock b/Cargo.lock index af349d38..5463b600 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.9.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4803cf8c252f374ae6bfbb341e49e5a37f7601f2ce74a105927a663eba952c67" +checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" dependencies = [ "enumn", "serde", @@ -30,77 +30,72 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.13.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee8cf1202a4f94d31837f1902ab0a75c77b65bf59719e093703abe83efd74ec" +checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" dependencies = [ "accesskit", - "parking_lot", ] [[package]] name = "accesskit_macos" -version = "0.5.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10be25f2b27bc33aa1647072e86b948b41596f1af1ae43a2b4b9be5d2011cbda" +checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" dependencies = [ "accesskit", "accesskit_consumer", "objc2", "once_cell", - "parking_lot", ] [[package]] name = "accesskit_unix" -version = "0.2.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630e7ee8f93c6246478bf0df6760db899b28d9ad54353a5f2d3157138ba817fc" +checksum = "e084cb5168790c0c112626175412dc5ad127083441a8248ae49ddf6725519e83" dependencies = [ "accesskit", "accesskit_consumer", "async-channel", "atspi", "futures-lite", - "parking_lot", "serde", "zbus", ] [[package]] name = "accesskit_windows" -version = "0.12.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13c462fabdd950ef14308a9390b07fa2e2e3aabccba1f3ea36ea2231bb942ab" +checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" dependencies = [ "accesskit", "accesskit_consumer", "arrayvec", "once_cell", - "parking_lot", "paste", - "windows 0.42.0", + "windows 0.48.0", ] [[package]] name = "accesskit_winit" -version = "0.10.0" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17727888757ec027ec221db33070e226ee07df44425b583bc67684204d35eff9" +checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" dependencies = [ "accesskit", "accesskit_macos", "accesskit_unix", "accesskit_windows", - "parking_lot", "winit", ] [[package]] name = "addr2line" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -171,7 +166,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -190,35 +185,41 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "alox-48" -version = "0.2.0" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8054396c448cd2fb78b7b719b9fe616eac0b6b0e824f1cbcc0804617124a3d3f" dependencies = [ - "bytemuck", - "color-eyre", "enum-as-inner", - "indexmap", + "indexmap 2.0.0", + "miette", "paste", - "pretty-hex", "serde", "thiserror", ] [[package]] name = "alsa" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" +checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47" dependencies = [ "alsa-sys", - "bitflags", + "bitflags 1.3.2", "libc", "nix 0.24.3", ] @@ -235,12 +236,12 @@ dependencies = [ [[package]] name = "android-activity" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" +checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" dependencies = [ "android-properties", - "bitflags", + "bitflags 1.3.2", "cc", "jni-sys", "libc", @@ -248,7 +249,7 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "num_enum", + "num_enum 0.6.1", ] [[package]] @@ -257,11 +258,20 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arboard" @@ -289,9 +299,18 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] [[package]] name = "async-broadcast" @@ -305,9 +324,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -323,7 +342,7 @@ dependencies = [ "async-lock", "async-task", "concurrent-queue", - "fastrand", + "fastrand 1.9.0", "futures-lite", "slab", ] @@ -369,7 +388,7 @@ dependencies = [ "log", "parking", "polling", - "rustix", + "rustix 0.37.23", "slab", "socket2", "waker-fn", @@ -377,9 +396,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] @@ -409,8 +428,8 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", - "signal-hook 0.3.15", + "rustix 0.37.23", + "signal-hook 0.3.17", "windows-sys 0.48.0", ] @@ -422,7 +441,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] @@ -459,20 +478,20 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "atk-sys" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" +checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" dependencies = [ "glib-sys", "gobject-sys", @@ -482,12 +501,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "atomic-waker" @@ -497,15 +513,15 @@ checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atomic_refcell" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" +checksum = "112ef6b3f6cb3cb6fc5b6b494ef7a848492cff1ab0ef4de10b0f7d572861c905" [[package]] name = "atspi" -version = "0.8.7" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab84c09a770065868da0d713f1f4b35af85d96530a868f1c1a6c249178379187" +checksum = "674e7a3376837b2e7d12d34d58ac47073c491dc3bf6f71a7adaf687d4d817faa" dependencies = [ "async-recursion", "async-trait", @@ -520,17 +536,12 @@ dependencies = [ [[package]] name = "atspi-macros" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ebc5a6f61f6996eca56a4cece7b3fe7da3b86f0473c7b71ab44e229f3acce4" +checksum = "97fb4870a32c0eaa17e35bca0e6b16020635157121fb7d45593d242c295bc768" dependencies = [ - "proc-macro2", "quote", - "serde", "syn 1.0.109", - "zbus", - "zbus_names", - "zvariant", ] [[package]] @@ -541,15 +552,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.6.2", + "miniz_oxide 0.7.1", "object", "rustc-demangle", ] @@ -568,9 +579,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bincode" @@ -587,7 +598,7 @@ version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -628,6 +639,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] + [[package]] name = "block" version = "0.1.6" @@ -681,22 +701,22 @@ dependencies = [ "async-lock", "async-task", "atomic-waker", - "fastrand", + "fastrand 1.9.0", "futures-lite", "log", ] [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" dependencies = [ "bytemuck_derive", ] @@ -709,7 +729,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] @@ -732,9 +752,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cairo-sys-rs" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" +checksum = "bd4d115132e01c0165e3bf5f56aedee8980b0b96ede4eb000b693c05a8adb8ff" dependencies = [ "libc", "system-deps", @@ -742,10 +762,11 @@ dependencies = [ [[package]] name = "calloop" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" dependencies = [ + "bitflags 1.3.2", "log", "nix 0.25.1", "slotmap", @@ -753,22 +774,29 @@ dependencies = [ "vec_map", ] +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" + [[package]] name = "catppuccin-egui" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c840bcad5f36bba893de10ad208359b4b6bab36c60262af8e7883f2843350475" +checksum = "1d3754c4417c19dc020529b0a3fb5e649afa59b16d49c9231210800ce00891b5" dependencies = [ "egui", ] [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -788,9 +816,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.1" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" dependencies = [ "smallvec", "target-lexicon", @@ -834,9 +862,15 @@ checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.4", ] +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + [[package]] name = "clipboard-win" version = "4.5.0" @@ -848,6 +882,47 @@ dependencies = [ "winapi", ] +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color-eyre" version = "0.6.2" @@ -881,6 +956,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "com-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" + [[package]] name = "combine" version = "4.6.6" @@ -909,6 +990,28 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "proc-macro-hack", + "tiny-keccak", +] + [[package]] name = "const_fn" version = "0.4.9" @@ -960,7 +1063,7 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", @@ -969,13 +1072,12 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", - "foreign-types", "libc", ] @@ -985,7 +1087,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation-sys 0.6.2", "coreaudio-sys", ] @@ -1026,9 +1128,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -1039,6 +1141,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32fast" version = "1.3.2" @@ -1048,6 +1165,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -1071,22 +1202,32 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.8.0", + "memoffset 0.9.0", "scopeguard", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -1124,17 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" dependencies = [ "lab", - "phf 0.11.1", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", + "phf 0.11.2", ] [[package]] @@ -1163,9 +1294,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.61+curl-8.0.1" +version = "0.4.65+curl-8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" +checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" dependencies = [ "cc", "libc", @@ -1177,14 +1308,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "d3d12" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" +dependencies = [ + "bitflags 1.3.2", + "libloading 0.7.4", + "winapi", +] + [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core", @@ -1204,9 +1346,15 @@ checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" [[package]] name = "deltae" -version = "0.3.1" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + +[[package]] +name = "deranged" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef311e2c0a388b9ba56c839ac68d33b92d18ce7104d0acc4375cb479e2b9a53" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "derivative" @@ -1230,9 +1378,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", @@ -1293,11 +1441,29 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.0", +] + +[[package]] +name = "dlv-list" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "d529fd73d344663edfd598ccb3f344e46034db51ebd103518eae34338248ad73" dependencies = [ - "libloading", + "const-random", +] + +[[package]] +name = "document-features" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" +dependencies = [ + "litrs", ] [[package]] @@ -1306,11 +1472,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "duplicate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +dependencies = [ + "heck", + "proc-macro-error", +] + [[package]] name = "ecolor" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f99fe3cac305af9d6d92971af60d0f7ea4d783201ef1673571567b6699964d9" +checksum = "2e479a7fa3f23d4e794f8b2f8b3568dd4e47886ad1b12c9c095e141cb591eb63" dependencies = [ "bytemuck", "serde", @@ -1318,90 +1494,114 @@ dependencies = [ [[package]] name = "eframe" -version = "0.21.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df3ce60931e5f2d83bab4484d1a283908534d5308cc6b0c5c22c59cd15ee7cc" +checksum = "bf4596583a2c680c55b6feaa748f74890c4f9cb9c7cb69d6117110444cb65b2f" dependencies = [ "bytemuck", + "cocoa", "directories-next", "egui", + "egui-wgpu", "egui-winit", "egui_glow", "glow", "glutin", "glutin-winit", + "image 0.24.7", "js-sys", + "log", + "objc", "percent-encoding", + "pollster", "raw-window-handle", "ron", "serde", "thiserror", - "tracing", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "wgpu", + "winapi", "winit", ] [[package]] name = "egui" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6412a21e0bde7c0918f7fb44bbbb86b5e1f88e63c026a4e747cc7af02f76dfbe" +checksum = "a3aef8ec3ae1b772f340170c65bf27d5b8c28f543a0116c844d2ac08d01123e7" dependencies = [ "accesskit", "ahash 0.8.3", "epaint", + "log", "nohash-hasher", "ron", "serde", - "tracing", ] [[package]] name = "egui-notify" -version = "0.6.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa87d2d91ebea916ae30837068f869f9b91242107f86f6abc3f085fbd160e195" +checksum = "d57ed9c398e24c1b9faf2c52cdc305dd29cb1d9dfa12a0166d254582bc47727a" dependencies = [ "egui", ] +[[package]] +name = "egui-wgpu" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33caaedd8283779c787298af23d8754a7e88421ff32e89ad0040c855fc0b0224" +dependencies = [ + "bytemuck", + "epaint", + "log", + "thiserror", + "type-map", + "wgpu", + "winit", +] + [[package]] name = "egui-winit" -version = "0.21.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab43597ba41f0ce39a364ad83185594578bfd8b3409b99dbcbb01df23afc3dbb" +checksum = "4a49155fd4a0a4fb21224407a91de0030847972ef90fc64edb63621caea61cb2" dependencies = [ "accesskit_winit", - "android-activity", "arboard", "egui", "instant", + "log", + "raw-window-handle", "serde", "smithay-clipboard", - "tracing", "webbrowser", "winit", ] [[package]] name = "egui_dock" -version = "0.4.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be7e6eb63cb936413bd2a4f54be4a9ef53a48252f25864f5f946d4954d7332bd" +checksum = "1ec07302c1a474f37fe6ef2c6672427880025edc37ac33955e6ea4a11bc6972a" dependencies = [ + "duplicate", "egui", + "paste", ] [[package]] name = "egui_extras" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f051342e97dfa2445107cb7d2e720617f5c840199b5cb4fe0ffcf481fcf5cce" +checksum = "9278f4337b526f0d57e5375e5a7340a311fa6ee8f9fcc75721ac50af13face02" dependencies = [ "egui", - "image 0.24.6", + "image 0.24.7", "resvg", "serde", "tiny-skia", @@ -1410,30 +1610,30 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8257332fb168a965b3dca81d6a344e053153773c889cabdba0b3b76f1629ae81" +checksum = "1f8c2752cdf1b0ef5fcda59a898cacabad974d4f5880e92a420b2c917022da64" dependencies = [ "bytemuck", "egui", "glow", + "log", "memoffset 0.6.5", - "tracing", "wasm-bindgen", "web-sys", ] [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ecd80612937e0267909d5351770fe150004e24dab93954f69ca62eecd3f77e" +checksum = "3857d743a6e0741cdd60b622a74c7a36ea75f5f8f11b793b41d905d2c9721a4b" dependencies = [ "bytemuck", "serde", @@ -1441,23 +1641,23 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] [[package]] name = "enum-as-inner" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] @@ -1478,25 +1678,25 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "enumn" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48016319042fb7c87b78d2993084a831793a897a5cd1a2a67cab9d1eeb4b7d76" +checksum = "b893c4eb2dc092c811165f84dc7447fae16fb66521717968c34c509b39b1a5c5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "epaint" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e78b5c58a1f7f621f9d546add2adce20636422c9b251e29f749e8a2f713c95" +checksum = "09333964d4d57f40a85338ba3ca5ed4716070ab184dcfed966b35491c5c64f3b" dependencies = [ "ab_glyph", "ahash 0.8.3", @@ -1504,16 +1704,23 @@ dependencies = [ "bytemuck", "ecolor", "emath", + "log", "nohash-hasher", "parking_lot", "serde", ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1548,15 +1755,15 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exr" -version = "1.6.3" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4" +checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" dependencies = [ "bit_field", "flume 0.10.14", "half", "lebe", - "miniz_oxide 0.6.2", + "miniz_oxide 0.7.1", "rayon-core", "smallvec", "zune-inflate", @@ -1574,9 +1781,9 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.7.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6b8560a05112eb52f04b00e5d3790c0dd75d9d980eb8a122fb23b92a623ccf" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ "bit-set", "regex", @@ -1591,6 +1798,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "fdeflate" version = "0.3.0" @@ -1610,6 +1823,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", +] + [[package]] name = "finl_unicode" version = "1.2.0" @@ -1624,9 +1849,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "miniz_oxide 0.7.1", @@ -1685,13 +1910,22 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.28" @@ -1746,7 +1980,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", + "fastrand 1.9.0", "futures-core", "futures-io", "memchr", @@ -1763,7 +1997,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] @@ -1798,9 +2032,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" dependencies = [ "gio-sys", "glib-sys", @@ -1811,9 +2045,9 @@ dependencies = [ [[package]] name = "gdk-sys" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a" +checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1859,9 +2093,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", @@ -1892,15 +2126,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "gio-sys" -version = "0.16.3" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" dependencies = [ "glib-sys", "gobject-sys", @@ -1942,11 +2176,20 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" +dependencies = [ + "bytemuck", +] + [[package]] name = "glib-sys" -version = "0.16.3" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" dependencies = [ "libc", "system-deps", @@ -1972,9 +2215,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" dependencies = [ "js-sys", "slotmap", @@ -1984,11 +2227,11 @@ dependencies = [ [[package]] name = "glutin" -version = "0.30.7" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89bab9ec7715de13d5d5402238e66f48e3a5ae636ebb45aba4013c962e2ff15" +checksum = "8fc93b03242719b8ad39fb26ed2b01737144ce7bd4bfc7adadcef806596760fe" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg_aliases", "cgl", "core-foundation", @@ -1996,7 +2239,7 @@ dependencies = [ "glutin_egl_sys", "glutin_glx_sys", "glutin_wgl_sys", - "libloading", + "libloading 0.7.4", "objc2", "once_cell", "raw-window-handle", @@ -2019,9 +2262,9 @@ dependencies = [ [[package]] name = "glutin_egl_sys" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5aaf0abb5c4148685b33101ae326a207946b4d3764d6cdc79f8316cdaa8367d" +checksum = "af784eb26c5a68ec85391268e074f0aa618c096eadb5d6330b0911cf34fe57c5" dependencies = [ "gl_generator", "windows-sys 0.45.0", @@ -2048,20 +2291,72 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" dependencies = [ "glib-sys", "libc", "system-deps", ] +[[package]] +name = "gpu-alloc" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +dependencies = [ + "bitflags 1.3.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "gpu-allocator" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +dependencies = [ + "backtrace", + "log", + "thiserror", + "winapi", + "windows 0.44.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" +dependencies = [ + "bitflags 1.3.2", + "gpu-descriptor-types", + "hashbrown 0.12.3", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "gtk-sys" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3" +checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" dependencies = [ "atk-sys", "cairo-sys-rs", @@ -2094,25 +2389,43 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "hassle-rs" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" dependencies = [ + "bitflags 1.3.2", + "com-rs", "libc", + "libloading 0.7.4", + "thiserror", + "widestring", + "winapi", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -2120,6 +2433,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "hkdf" version = "0.10.0" @@ -2140,6 +2459,21 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "hound" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" + [[package]] name = "http" version = "0.2.9" @@ -2198,9 +2532,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2221,14 +2555,14 @@ dependencies = [ "num-traits", "png", "qoi", - "tiff", + "tiff 0.8.1", ] [[package]] name = "image" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", @@ -2240,7 +2574,7 @@ dependencies = [ "num-traits", "png", "qoi", - "tiff", + "tiff 0.9.0", ] [[package]] @@ -2262,7 +2596,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", "serde", ] @@ -2272,6 +2616,26 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -2286,11 +2650,11 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", "windows-sys 0.48.0", ] @@ -2329,18 +2693,18 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jni" @@ -2412,19 +2776,50 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading 0.7.4", + "pkg-config", +] + [[package]] name = "khronos_api" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kurbo" version = "0.8.3" @@ -2467,11 +2862,22 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + [[package]] name = "libc" -version = "0.2.142" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -2483,17 +2889,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libnghttp2-sys" -version = "0.1.7+1.45.0" +version = "0.1.8+1.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" dependencies = [ "cc", "libc", @@ -2501,9 +2917,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -2528,15 +2944,27 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "litrs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -2544,11 +2972,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ - "cfg-if", "value-bag", ] @@ -2558,7 +2985,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -2567,11 +2994,15 @@ version = "0.4.0" dependencies = [ "alox-48", "atomic_refcell", + "bitflags 2.4.0", "bytemuck", + "camino", "catppuccin-egui", "cfg-if", "color-eyre", "command-lib", + "crc", + "crossbeam", "dashmap", "eframe", "egui", @@ -2582,12 +3013,14 @@ dependencies = [ "eyre", "futures", "git-version", - "image 0.24.6", + "glam", + "image 0.24.7", "itertools", "luminol-term", - "num-derive", - "num-traits", + "notify", + "num_enum 0.7.0", "once_cell", + "ouroboros", "parking_lot", "paste", "poll-promise", @@ -2596,6 +3029,8 @@ dependencies = [ "rmxp-types", "rodio", "ron", + "rust-ini", + "rustysynth", "serde", "slab", "static_assertions", @@ -2603,10 +3038,11 @@ dependencies = [ "strum", "surf", "syntect", + "thiserror", "tracing", "tracing-subscriber", - "windows 0.46.0", - "winreg 0.11.0", + "windows 0.51.1", + "winreg 0.51.0", "winres", "zip", ] @@ -2625,9 +3061,9 @@ dependencies = [ [[package]] name = "mac_address" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b238e3235c8382b7653c6408ed1b08dd379bdb9fdf990fb0bbae3db2cc0ae963" +checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" dependencies = [ "nix 0.23.2", "winapi", @@ -2692,13 +3128,50 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "mime" version = "0.3.17" @@ -2731,15 +3204,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -2752,14 +3216,34 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "naga" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "codespan-reporting", + "hexf-parse", + "indexmap 1.9.3", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", ] [[package]] @@ -2768,7 +3252,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -2777,10 +3261,10 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "jni-sys", "ndk-sys", - "num_enum", + "num_enum 0.5.11", "raw-window-handle", "thiserror", ] @@ -2806,7 +3290,7 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if", "libc", @@ -2819,7 +3303,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", @@ -2832,7 +3316,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", @@ -2845,7 +3329,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", @@ -2868,6 +3352,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2912,30 +3415,48 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ - "autocfg", + "num_enum_derive 0.5.11", ] [[package]] -name = "num_cpus" -version = "1.15.0" +name = "num_enum" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" dependencies = [ - "hermit-abi 0.2.6", - "libc", + "num_enum_derive 0.6.1", ] [[package]] name = "num_enum" -version = "0.5.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.7.0", ] [[package]] @@ -2950,6 +3471,30 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "objc" version = "0.2.7" @@ -2957,6 +3502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -2978,9 +3524,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" [[package]] name = "objc2" -version = "0.3.0-beta.3" +version = "0.3.0-beta.3.patch-leaks.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe31e5425d3d0b89a15982c024392815da40689aceb34bad364d58732bcfd649" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" dependencies = [ "block2", "objc-sys", @@ -2996,6 +3542,15 @@ dependencies = [ "objc-sys", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -3007,9 +3562,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] @@ -3037,11 +3592,20 @@ dependencies = [ "cc", ] +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -3057,9 +3621,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -3069,22 +3633,32 @@ dependencies = [ [[package]] name = "orbclient" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221d488cd70617f1bd599ed8ceb659df2147d9393717954d82a0f5e8032a6ab1" +checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" dependencies = [ "redox_syscall 0.3.5", ] [[package]] name = "ordered-float" -version = "3.7.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" +checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3095,6 +3669,31 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ouroboros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c86de06555b970aec45229b27291b53154f21a5743a163419f4e4c0b065dcde" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cad0c4b129e9696e37cb712b243777b90ef489a0bfaa0ac34e7d9b860e4f134" +dependencies = [ + "heck", + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "overload" version = "0.1.1" @@ -3118,9 +3717,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "pango-sys" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" dependencies = [ "glib-sys", "gobject-sys", @@ -3146,25 +3745,25 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "backtrace", "cfg-if", "libc", "petgraph", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", "thread-id", - "windows-sys 0.45.0", + "windows-targets 0.48.5", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "peeking_take_while" @@ -3174,15 +3773,15 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -3190,9 +3789,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -3200,36 +3799,36 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "pest_meta" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.0.0", ] [[package]] @@ -3243,45 +3842,45 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", - "phf_shared 0.11.1", + "phf_shared 0.11.2", ] [[package]] name = "phf_codegen" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ "phf_generator", - "phf_shared 0.11.1", + "phf_shared 0.11.2", ] [[package]] name = "phf_generator" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ - "phf_shared 0.11.1", + "phf_shared 0.11.2", "rand 0.8.5", ] [[package]] name = "phf_macros" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", - "phf_shared 0.11.1", + "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] @@ -3295,9 +3894,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] @@ -3310,29 +3909,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -3342,31 +3941,31 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ - "base64 0.21.0", - "indexmap", + "base64 0.21.2", + "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", - "time 0.3.20", + "time 0.3.27", ] [[package]] name = "png" -version = "0.17.8" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", @@ -3375,9 +3974,11 @@ dependencies = [ [[package]] name = "poll-promise" -version = "0.2.0" -source = "git+https://github.com/EmbarkStudios/poll-promise?rev=8c4d0d6e4d693cf54e9306950cb2f6d271c77080#8c4d0d6e4d693cf54e9306950cb2f6d271c77080" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6a58fecbf9da8965bcdb20ce4fd29788d1acee68ddbb64f0ba1b81bccdb7df" dependencies = [ + "document-features", "smol", "static_assertions", ] @@ -3389,7 +3990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", - "bitflags", + "bitflags 1.3.2", "cfg-if", "concurrent-queue", "libc", @@ -3398,6 +3999,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "polyval" version = "0.4.5" @@ -3415,7 +4022,7 @@ version = "0.8.1" source = "git+https://github.com/wez/wezterm?rev=3666303c7b26c6c966b3f136dbb954686d334cc3#3666303c7b26c6c966b3f136dbb954686d334cc3" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "downcast-rs", "filedescriptor", "lazy_static", @@ -3435,12 +4042,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "pretty-hex" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3451,6 +4052,30 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -3459,13 +4084,19 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46b2164ebdb1dfeec5e337be164292351e11daf63a05174c6776b2f47460f0c9" + [[package]] name = "qoi" version = "0.4.1" @@ -3477,18 +4108,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -3552,7 +4183,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -3564,6 +4195,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -3604,7 +4241,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -3613,7 +4250,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -3622,33 +4259,45 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "regex-automata" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "renderdoc-sys" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "resvg" @@ -3666,14 +4315,12 @@ dependencies = [ [[package]] name = "rfd" -version = "0.11.3" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb2988ec50c9bcdb0c012b89643a6094a35a785a37897211ee62e1639342f7b" +checksum = "241a0deb168c88050d872294f7b3106c1dfa8740942bcc97bc91b98e97b5c501" dependencies = [ - "async-io", "block", "dispatch", - "futures-util", "glib-sys", "gobject-sys", "gtk-sys", @@ -3686,7 +4333,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows 0.44.0", + "windows-sys 0.48.0", ] [[package]] @@ -3704,9 +4351,11 @@ version = "0.1.0" dependencies = [ "alox-48", "bytemuck", + "camino", "flate2", "num-derive", "num-traits", + "num_enum 0.6.1", "paste", "rand 0.8.5", "serde", @@ -3720,19 +4369,23 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf1d4dea18dff2e9eb6dca123724f8b60ef44ad74a9ad283cdfe025df7e73fa" dependencies = [ + "claxon", "cpal", + "hound", + "lewton", "symphonia", ] [[package]] name = "ron" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.13.1", - "bitflags", + "base64 0.21.2", + "bitflags 2.4.0", "serde", + "serde_derive", ] [[package]] @@ -3744,6 +4397,16 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3767,29 +4430,48 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.15" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "rustysynth" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b916492c4bdd1dba14c92761cc855f9ef12f3c7e4ef18cab07ee49e784c1c66b" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" @@ -3808,11 +4490,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -3823,9 +4505,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" @@ -3875,41 +4557,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.187" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "30a7fe14252655bd1e578af19f5fa00fe02fd0013b100ca6b49fde31c41bae4c" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-xml-rs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0bf1ba0696ccf0872866277143ff1fd14d22eec235d2b23702f95e6660f7dfa" -dependencies = [ - "log", - "serde", - "thiserror", - "xml-rs", -] - [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.187" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -3929,20 +4599,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -4018,7 +4688,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -4042,13 +4712,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -4094,9 +4764,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -4113,9 +4783,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simplecss" @@ -4128,15 +4798,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", "serde", @@ -4164,9 +4834,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smithay-client-toolkit" @@ -4174,7 +4844,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" dependencies = [ - "bitflags", + "bitflags 1.3.2", "calloop", "dlib", "lazy_static", @@ -4242,6 +4912,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags 1.3.2", + "num-traits", +] + [[package]] name = "standback" version = "0.2.17" @@ -4312,7 +4992,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37769f3fb7cc26bf69609e455a606c073d7332ddd45742b2317d72abafc04d8e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "lazy_static", "steamworks-sys", "thiserror", @@ -4332,9 +5012,9 @@ checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" [[package]] name = "strict-num" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" dependencies = [ "float-cmp", ] @@ -4347,24 +5027,24 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] @@ -4384,155 +5064,70 @@ dependencies = [ "cfg-if", "encoding_rs", "futures-util", - "getrandom 0.2.9", + "getrandom 0.2.10", "http-client", "http-types", "log", - "mime_guess", - "once_cell", - "pin-project-lite", - "serde", - "serde_json", - "web-sys", -] - -[[package]] -name = "svgtypes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" -dependencies = [ - "siphasher", -] - -[[package]] -name = "symphonia" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3671dd6f64f4f9d5c87179525054cfc1f60de23ba1f193bd6ceab812737403f1" -dependencies = [ - "lazy_static", - "symphonia-bundle-flac", - "symphonia-bundle-mp3", - "symphonia-codec-aac", - "symphonia-codec-adpcm", - "symphonia-codec-pcm", - "symphonia-codec-vorbis", - "symphonia-core", - "symphonia-format-isomp4", - "symphonia-format-wav", - "symphonia-metadata", -] - -[[package]] -name = "symphonia-bundle-flac" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc2deed3204967871ba60f913378f95820cb47a2fe9b2eef5a9eedb417dfdc8" -dependencies = [ - "log", - "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-bundle-mp3" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a0846e7a2c9a8081ff799fc83a975170417ad2a143f644a77ec2e3e82a2b73" -dependencies = [ - "bitflags", - "lazy_static", - "log", - "symphonia-core", - "symphonia-metadata", -] - -[[package]] -name = "symphonia-codec-aac" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdd4a10695ca0528572360ec020586320357350eb62791693667e7de8c871a" -dependencies = [ - "lazy_static", - "log", - "symphonia-core", + "mime_guess", + "once_cell", + "pin-project-lite", + "serde", + "serde_json", + "web-sys", ] [[package]] -name = "symphonia-codec-adpcm" -version = "0.5.2" +name = "svgtypes" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5cfb8d4405e26eb9593157dc45b05e102b8d774b38ed2a95946d6bb9e26e3e" +checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" dependencies = [ - "log", - "symphonia-core", + "siphasher", ] [[package]] -name = "symphonia-codec-pcm" -version = "0.5.2" +name = "symphonia" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb9a9f0b9991cccf3217b74644af412d5d082a4815e5e2943f26e0ecabdf3c9" +checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" dependencies = [ - "log", + "lazy_static", + "symphonia-bundle-mp3", "symphonia-core", + "symphonia-metadata", ] [[package]] -name = "symphonia-codec-vorbis" -version = "0.5.2" +name = "symphonia-bundle-mp3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfed6f7b6bfa21d7cef1acefc8eae5db80df1608a1aca91871b07cbd28d7b74" +checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a" dependencies = [ + "bitflags 1.3.2", + "lazy_static", "log", "symphonia-core", - "symphonia-utils-xiph", + "symphonia-metadata", ] [[package]] name = "symphonia-core" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9567e2d8a5f866b2f94f5d366d811e0c6826babcff6d37de9e1a6690d38869" +checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" dependencies = [ "arrayvec", - "bitflags", + "bitflags 1.3.2", "bytemuck", "lazy_static", "log", ] -[[package]] -name = "symphonia-format-isomp4" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1818f6f54b4eaba5ec004a8dbcca637c57e617eb1ff4c9addbd3fc065eba437" -dependencies = [ - "encoding_rs", - "log", - "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", -] - -[[package]] -name = "symphonia-format-wav" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06679bd5646b3037300f88891dfc8a6e1cc4e1133206cc17a98e5d7c22f88296" -dependencies = [ - "log", - "symphonia-core", - "symphonia-metadata", -] - [[package]] name = "symphonia-metadata" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd35c263223ef6161000be79b124a75de3e065eea563bf3ef169b3e94c7bb2e" +checksum = "89c3e1937e31d0e068bbe829f66b2f2bfaa28d056365279e0ef897172c3320c0" dependencies = [ "encoding_rs", "lazy_static", @@ -4540,16 +5135,6 @@ dependencies = [ "symphonia-core", ] -[[package]] -name = "symphonia-utils-xiph" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce340a6c33ac06cb42de01220308ec056e8a2a3d5cc664aaf34567392557136b" -dependencies = [ - "symphonia-core", - "symphonia-metadata", -] - [[package]] name = "syn" version = "1.0.109" @@ -4563,9 +5148,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -4574,21 +5159,19 @@ dependencies = [ [[package]] name = "syntect" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c454c27d9d7d9a84c7803aaa3c50cd088d2906fe3c6e42da3209aa623576a8" +checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91" dependencies = [ "bincode", - "bitflags", + "bitflags 1.3.2", "fancy-regex", "flate2", "fnv", - "lazy_static", "once_cell", "plist", - "regex-syntax 0.6.29", + "regex-syntax", "serde", - "serde_derive", "serde_json", "thiserror", "walkdir", @@ -4597,34 +5180,43 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.5" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fe581ad25d11420b873cf9aedaca0419c2b411487b134d4d21065f3d092055" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml 0.7.3", + "toml 0.7.6", "version-compare", ] [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.5.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", + "rustix 0.38.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", ] [[package]] @@ -4636,7 +5228,7 @@ dependencies = [ "dirs", "fnv", "nom", - "phf 0.11.1", + "phf 0.11.2", "phf_codegen", ] @@ -4664,8 +5256,8 @@ version = "0.22.0" source = "git+https://github.com/wez/wezterm?rev=3666303c7b26c6c966b3f136dbb954686d334cc3#3666303c7b26c6c966b3f136dbb954686d334cc3" dependencies = [ "anyhow", - "base64 0.21.0", - "bitflags", + "base64 0.21.2", + "bitflags 1.3.2", "filedescriptor", "finl_unicode", "fixedbitset", @@ -4703,29 +5295,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "thread-id" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" +checksum = "79474f573561cdc4871a0de34a51c92f7f5a56039113fbb5b9c9f96bdb756669" dependencies = [ "libc", "redox_syscall 0.2.16", @@ -4753,6 +5345,17 @@ dependencies = [ "weezl", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.2.27" @@ -4770,21 +5373,22 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07" dependencies = [ + "deranged", "itoa", "serde", "time-core", - "time-macros 0.2.8", + "time-macros 0.2.13", ] [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" @@ -4798,9 +5402,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9" dependencies = [ "time-core", ] @@ -4818,6 +5422,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-skia" version = "0.8.4" @@ -4869,9 +5482,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", @@ -4881,20 +5494,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", @@ -4903,10 +5516,11 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.38" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4915,20 +5529,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -4981,9 +5595,18 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.19.0" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" + +[[package]] +name = "type-map" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] [[package]] name = "typenum" @@ -4993,9 +5616,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "uds_windows" @@ -5009,9 +5632,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -5024,9 +5647,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -5043,6 +5666,18 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.4.1" @@ -5055,9 +5690,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -5093,12 +5728,12 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "atomic", - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -5109,13 +5744,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "vcpkg" @@ -5179,9 +5810,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5189,24 +5820,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -5216,9 +5847,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5226,22 +5857,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wayland-client" @@ -5249,7 +5880,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" dependencies = [ - "bitflags", + "bitflags 1.3.2", "downcast-rs", "libc", "nix 0.24.3", @@ -5288,7 +5919,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "wayland-client", "wayland-commons", "wayland-scanner", @@ -5330,9 +5961,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -5340,12 +5971,12 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b692165700260bbd40fbc5ff23766c03e339fbaca907aeea5cb77bf0a553ca83" +checksum = "b2c79b77f525a2d670cb40619d7d9c673d09e0666f72c591ebd7861f84a87e57" dependencies = [ "core-foundation", - "dirs", + "home", "jni 0.21.1", "log", "ndk-context", @@ -5375,7 +6006,7 @@ name = "wezterm-blob-leases" version = "0.1.0" source = "git+https://github.com/wez/wezterm?rev=3666303c7b26c6c966b3f136dbb954686d334cc3#3666303c7b26c6c966b3f136dbb954686d334cc3" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "mac_address", "once_cell", "sha2 0.9.9", @@ -5422,7 +6053,7 @@ version = "0.1.0" source = "git+https://github.com/wez/wezterm?rev=3666303c7b26c6c966b3f136dbb954686d334cc3#3666303c7b26c6c966b3f136dbb954686d334cc3" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "csscolorparser", "finl_unicode", "hex", @@ -5443,6 +6074,112 @@ dependencies = [ "wezterm-dynamic", ] +[[package]] +name = "wgpu" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd" +dependencies = [ + "arrayvec", + "cfg-if", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.4.0", + "codespan-reporting", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.4.0", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "glow", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.0", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a" +dependencies = [ + "bitflags 2.4.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -5485,43 +6222,57 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.42.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0286ba339aa753e70765d521bb0242cc48e1194562bfa2a2ad7ac8a6de28f5d5" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ - "windows-implement", - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.42.2", ] [[package]] name = "windows" -version = "0.44.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.42.2", + "windows-implement", + "windows-interface", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] name = "windows-implement" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9539b6bd3eadbd9de66c9666b22d802b833da7e996bc06896142e09854a61767" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" dependencies = [ "proc-macro2", "quote", @@ -5529,18 +6280,14 @@ dependencies = [ ] [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-interface" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -5558,7 +6305,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] @@ -5578,17 +6325,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -5599,9 +6346,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" @@ -5611,9 +6358,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" @@ -5623,9 +6370,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" @@ -5635,9 +6382,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" @@ -5647,9 +6394,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" @@ -5659,9 +6406,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" @@ -5671,18 +6418,18 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winit" -version = "0.28.3" +version = "0.28.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f504e8c117b9015f618774f8d58cd4781f5a479bc41079c064f974cbb253874" +checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196" dependencies = [ "android-activity", - "bitflags", + "bitflags 1.3.2", "cfg_aliases", "core-foundation", "core-graphics", @@ -5712,9 +6459,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.4.4" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5617da7e1f97bf363947d767b91aaf3c2bbc19db7fda9c65af1278713d58e0a2" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] @@ -5730,12 +6477,12 @@ dependencies = [ [[package]] name = "winreg" -version = "0.11.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -5801,9 +6548,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" +checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" [[package]] name = "xmlparser" @@ -5822,18 +6569,20 @@ dependencies = [ [[package]] name = "zbus" -version = "3.12.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29242fa5ec5693629ae74d6eb1f69622a9511f600986d6d9779bccf36ac316e3" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io", "async-lock", + "async-process", "async-recursion", "async-task", "async-trait", + "blocking", "byteorder", "derivative", "enumflags2", @@ -5847,7 +6596,6 @@ dependencies = [ "ordered-stream", "rand 0.8.5", "serde", - "serde-xml-rs", "serde_repr", "sha1 0.10.5", "static_assertions", @@ -5862,9 +6610,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.12.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537793e26e9af85f774801dc52c6f6292352b2b517c5cf0449ffd3735732a53a" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5876,9 +6624,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" dependencies = [ "serde", "static_assertions", @@ -5887,9 +6635,9 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ "byteorder", "crc32fast", @@ -5899,18 +6647,18 @@ dependencies = [ [[package]] name = "zune-inflate" -version = "0.2.53" +version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440a08fd59c6442e4b846ea9b10386c38307eae728b216e1ab2c305d1c9daaf8" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] [[package]] name = "zvariant" -version = "3.12.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe4914a985446d6fd287019b5fceccce38303d71407d9e6e711d44954a05d8" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" dependencies = [ "byteorder", "enumflags2", @@ -5922,9 +6670,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.12.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34c20260af4b28b3275d6676c7e2a6be0d4332e8e0aba4616d34007fd84e462a" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5935,16 +6683,11 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b22993dbc4d128a17a3b6c92f1c63872dd67198537ee728d8b5d7c40640a8b" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] - -[[patch.unused]] -name = "egui_dock" -version = "0.5.0" -source = "git+https://github.com/Adanos020/egui_dock#9c5154bda8decfb6f187fc1e00e3f16a4115a8b5" diff --git a/Cargo.toml b/Cargo.toml index d0f046be..a4fec802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,19 +18,26 @@ build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -egui = "0.21" -eframe = { version = "0.21", features = ["persistence"] } -egui_extras = { version = "0.21", features = ["svg", "image"] } -egui-notify = "0.6" -egui_dock = "0.4" -catppuccin-egui = "2.0.1" +egui = "0.22" +eframe = { version = "0.22", features = [ + "persistence", + "wgpu", +], default-features = false } +egui_extras = { version = "0.22", features = ["svg", "image"] } +egui-notify = { version = "0.9" } # temporary patch +egui_dock = { version = "0.6" } +catppuccin-egui = "3.0.0" + +glam = { version = "0.24", features = ["bytemuck"] } image = { version = "0.24", features = ["jpeg", "png"] } -strum = { version = "0.24.1", features = ["derive"] } -enum-as-inner = "0.5.1" -num-traits = "0.2" -num-derive = "0.3" +strum = { version = "0.25", features = ["derive"] } +enum-as-inner = "0.6" +num_enum = "0.7" + +bitflags = "2.3" +ouroboros = "0.18" git-version = "0.3" @@ -40,10 +47,11 @@ parking_lot = { version = "0.12", features = [ "hardware-lock-elision", ] } atomic_refcell = "0.1.9" -dashmap = "5.4.0" +dashmap = "5.5" +crossbeam = "0.8" futures = "0.3" -poll-promise = { version = "0.2.0", features = ["smol"] } +poll-promise = { version = "0.3.0", features = ["smol"] } surf = "2.3.2" slab = { version = "0.4", features = ["serde"] } @@ -55,46 +63,47 @@ syntect = { version = "5.0", features = [ rand = "0.8.5" zip = { version = "0.6", default-features = false, features = ["deflate"] } +notify = "6.0" +rfd = "0.12" +camino = "1.1" -bytemuck = "1" +bytemuck = { version = "1.14", features = ["derive", "min_const_generics"] } color-eyre = "0.6" eyre = "0.6" +thiserror = "1.0" paste = "1.0" cfg-if = "1.0.0" serde = { version = "1.0", features = ["derive"] } -alox-48 = { version = "*", path = "alox-48" } +alox-48 = { version = "0.4.1", default-features = false } ron = "0.8.0" +rust-ini = "0.19.0" tracing = "0.1.37" tracing-subscriber = "0.3" -itertools = "0.10" +itertools = "0.11" static_assertions = "1.1.0" -rodio = { version = "0.17.0", features = [ - "symphonia-all", -], default-features = false } +rodio = { version = "0.17.1" } +rustysynth = "1.2" command-lib = { version = "*", path = "command-lib" } rmxp-types = { version = "*", path = "rmxp-types" } luminol-term = { version = "*", path = "luminol-term" } -rfd = "0.11" - steamworks = { version = "0.10.0", optional = true } +crc = { version = "3.0", optional = true } [features] -default = ["generic-tilemap"] -generic-tilemap = [] -steamworks = ["dep:steamworks"] +steamworks = ["dep:steamworks", "crc"] [target.'cfg(windows)'.dependencies] -windows = { version = "0.46.0", features = ["Win32_System_Registry"] } -winreg = "0.11.0" +windows = { version = "0.51.1", features = ["Win32_System_Registry"] } +winreg = "0.51.0" [target.'cfg(windows)'.build-dependencies] winres = "0.1" @@ -107,29 +116,46 @@ ProductName = "Luminol" [profile.release] opt-level = 3 lto = "fat" +debug = true # Enable only a small amount of optimization in debug mode [profile.dev] opt-level = 1 -# Enable max optimizations for dependencies, but not for our code: -[profile.dev.package."*"] +# Enable max optimizations for certain dependencies, but not for our code +# These dependencies are "hot" in that they are used quite frequently + +# Ui +[profile.dev.package.egui] +opt-level = 3 + +[profile.dev.package.eframe] +opt-level = 3 + +# Audio +[profile.dev.package.rodio] +opt-level = 3 + +[profile.dev.package.rustysynth] +opt-level = 3 + +# Graphics +[profile.dev.package.wgpu] +opt-level = 3 + +[profile.dev.package.image] +opt-level = 3 + +[profile.dev.package.glam] opt-level = 3 # See why config is set up this way. # https://bevy-cheatbook.github.io/pitfalls/performance.html#why-not-use---release [workspace] -members = ["alox-48", "command-lib", "rmxp-types", "luminol-term"] +members = ["command-lib", "rmxp-types", "luminol-term"] [patch.crates-io] -poll-promise = { git = "https://github.com/EmbarkStudios/poll-promise", rev = "8c4d0d6e4d693cf54e9306950cb2f6d271c77080" } -egui_dock = { git = "https://github.com/Adanos020/egui_dock" } -# js-sys = { git = "https://github.com/Speak2Erase/wasm-bindgen-filesystem-access-api" } -# web-sys = { git = "https://github.com/Speak2Erase/wasm-bindgen-filesystem-access-api" } -# wasm-bindgen = { git = "https://github.com/Speak2Erase/wasm-bindgen-filesystem-access-api" } - - # If you want to use the bleeding edge version of egui and eframe: # egui = { git = "https://github.com/emilk/egui", branch = "master" } # eframe = { git = "https://github.com/emilk/egui", branch = "master" } diff --git a/README.md b/README.md index 8565c215..a074bc9a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Luminol *may* use `Lua` for plugins in the future. It is something I am actively - [@Speak2Erase](https://github.com/Speak2Erase): Luminol's main contributor - [@somedevfox](https://github.com/somedevfox): Occasional contributor and creator of rsgss (a sister project of Luminol) -- [@Lionmeow](https://github.com/Lionmeow) designer of Luminol's icon and Lumi +- [@white-axe](https://github.com/white-axe): New contributor +- [@Lionmeow](https://github.com/Lionmeow): Designer of Luminol's icon and Lumi ## RGSS version support @@ -40,6 +41,9 @@ Compatibility: Native builds are the main focus at the moment, but no official releases will be made until Luminol is stable. Instead, you will have to compile luminol yourself, by grabbing your favorite nightly rust toolchain from [rustup](https://rustup.rs) and running `cargo build`. + +If you are on Linux, you will also need to grab clang and mold from your package manager. + Luminol has like a bajillion dependencies right now so it may take upwards of 15 minutes to compile. **You can not use one of the stable release channels.** @@ -60,10 +64,10 @@ Basic functionality: - [ ] Edit event commands - [x] View event commands - [x] Change tiles on map -- [ ] Multiple brush types -- [ ] Change autotiles on map -- [ ] Hardware accelerated tilemap -- [ ] Properly render blend modes and opacity +- [x] Multiple brush types +- [x] Change autotiles on map +- [x] Hardware accelerated tilemap +- [x] Properly render blend modes and opacity - [x] Sound test - [ ] Actor editor - [ ] Class editor diff --git a/alox-48 b/alox-48 deleted file mode 160000 index 833dfc7b..00000000 --- a/alox-48 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 833dfc7bc5c8c3ce0b5bff6b8f9c9050341e687b diff --git a/command-lib/Cargo.toml b/command-lib/Cargo.toml index 05b6c8ad..453ad195 100644 --- a/command-lib/Cargo.toml +++ b/command-lib/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } -strum = { version = "0.24", features = ["derive"] } +strum = { version = "0.25", features = ["derive"] } rand = "0.8.5" diff --git a/luminol-term/Cargo.toml b/luminol-term/Cargo.toml index b3132078..b5d2f627 100644 --- a/luminol-term/Cargo.toml +++ b/luminol-term/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -egui = "0.21.0" +egui = "0.22.0" # termwiz = "0.20.0" crossbeam-channel = "0.5" @@ -16,4 +16,4 @@ portable-pty = { version = "0.8.1", git = "https://github.com/wez/wezterm", rev termwiz = { version = "0.22.0", git = "https://github.com/wez/wezterm", rev = "3666303c7b26c6c966b3f136dbb954686d334cc3" } [dev-dependencies] -eframe = "0.21.0" +eframe = "0.22.0" diff --git a/luminol-term/src/lib.rs b/luminol-term/src/lib.rs index 57c11771..39f00e47 100644 --- a/luminol-term/src/lib.rs +++ b/luminol-term/src/lib.rs @@ -71,12 +71,10 @@ impl Terminal { loop { let Ok(len) = reader.read(&mut buf) else { - return + return; }; let actions = parser.parse_as_vec(&buf[0..len]); - let Ok(_) = sender.send(actions) else { - return - }; + let Ok(_) = sender.send(actions) else { return }; } }); diff --git a/rmxp-types/Cargo.toml b/rmxp-types/Cargo.toml index 81fc71da..32c2d97e 100644 --- a/rmxp-types/Cargo.toml +++ b/rmxp-types/Cargo.toml @@ -7,13 +7,15 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } -alox-48 = { version = "*", path = "../alox-48" } +alox-48 = { version = "0.4.1", default-features = false } bytemuck = { version = "1.13.1", features = ["extern_crate_alloc"] } num-traits = "0.2" num-derive = "0.3" slab = { version = "0.4", features = ["serde"] } -strum = { version = "0.24.1", features = ["derive"] } +strum = { version = "0.25", features = ["derive"] } paste = "1.0" +camino = "1.1" +num_enum = "0.6" rand = "0.8.5" flate2 = "1.0" diff --git a/rmxp-types/src/helpers/id.rs b/rmxp-types/src/helpers/id.rs new file mode 100644 index 00000000..902328b0 --- /dev/null +++ b/rmxp-types/src/helpers/id.rs @@ -0,0 +1,36 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + use serde::Deserialize; + + let id = std::num::NonZeroUsize::deserialize(deserializer)?; + + Ok(id.get() - 1) +} + +pub fn serialize(value: &usize, serializer: S) -> Result +where + S: serde::Serializer, +{ + use serde::Serialize; + + (value + 1).serialize(serializer) +} diff --git a/rmxp-types/src/helpers/id_vec.rs b/rmxp-types/src/helpers/id_vec.rs new file mode 100644 index 00000000..179c2f83 --- /dev/null +++ b/rmxp-types/src/helpers/id_vec.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a vec of nonzero usizes") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + + while let Some(value) = seq.next_element::()? { + values.push(value.get() - 1); + } + + Ok(values) + } + } + + deserializer.deserialize_seq(Visitor) +} + +pub fn serialize(values: &Vec, serializer: S) -> Result +where + S: serde::Serializer, +{ + use serde::ser::SerializeSeq; + + let mut seq = serializer.serialize_seq(Some(values.len()))?; + + for value in values { + seq.serialize_element(value)?; + } + + seq.end() +} diff --git a/rmxp-types/src/helpers/mod.rs b/rmxp-types/src/helpers/mod.rs new file mode 100644 index 00000000..f2ecdf10 --- /dev/null +++ b/rmxp-types/src/helpers/mod.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub mod id; +pub mod id_vec; +pub mod nil_padded; +pub mod optional_id; +pub mod optional_path; + +mod parameter_type; + +pub use parameter_type::*; diff --git a/rmxp-types/src/helpers/nil_padded.rs b/rmxp-types/src/helpers/nil_padded.rs new file mode 100644 index 00000000..5587114d --- /dev/null +++ b/rmxp-types/src/helpers/nil_padded.rs @@ -0,0 +1,79 @@ +// Copyright (C) 2022 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, + T: serde::Deserialize<'de>, +{ + struct Visitor { + _marker: core::marker::PhantomData, + } + + impl<'de, T> serde::de::Visitor<'de> for Visitor + where + T: serde::Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a nil padded array") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + use serde::de::Error; + + let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + + if let Some(v) = seq.next_element::>()? { + if v.is_some() { + return Err(A::Error::custom("the first element was not nil")); + } + } + + while let Some(ele) = seq.next_element::()? { + values.push(ele); + } + + Ok(values) + } + } + + deserializer.deserialize_seq(Visitor { + _marker: core::marker::PhantomData, + }) +} + +pub fn serialize(elements: &[T], serializer: S) -> Result +where + S: serde::Serializer, + T: serde::Serialize, +{ + use serde::ser::SerializeSeq; + + let mut seq = serializer.serialize_seq(Some(elements.len() + 1))?; + seq.serialize_element(&None::)?; + + for v in elements { + seq.serialize_element(v)?; + } + + seq.end() +} diff --git a/rmxp-types/src/helpers/optional_id.rs b/rmxp-types/src/helpers/optional_id.rs new file mode 100644 index 00000000..7af74a2b --- /dev/null +++ b/rmxp-types/src/helpers/optional_id.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + use serde::Deserialize; + + Ok(match usize::deserialize(deserializer)? { + 0 => None, + v => Some(v - 1), + }) +} + +pub fn serialize(value: &Option, serializer: S) -> Result +where + S: serde::Serializer, +{ + use serde::Serialize; + + match value { + Some(v) => (v + 1).serialize(serializer), + None => 0.serialize(serializer), + } +} diff --git a/rmxp-types/src/helpers/optional_path.rs b/rmxp-types/src/helpers/optional_path.rs new file mode 100644 index 00000000..da081415 --- /dev/null +++ b/rmxp-types/src/helpers/optional_path.rs @@ -0,0 +1,66 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use camino::Utf8PathBuf; + +pub fn serialize(path: &Option, serializer: S) -> Result +where + S: serde::Serializer, +{ + match path { + Some(path) => serializer.serialize_str(path.as_str()), + None => serializer.serialize_str(""), + } +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct Visitor; + + impl serde::de::Visitor<'_> for Visitor { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + if v.is_empty() { + Ok(None) + } else { + Ok(Some(v.into())) + } + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + if v.is_empty() { + Ok(None) + } else { + Ok(Some(v.into())) + } + } + } + + deserializer.deserialize_string(Visitor) +} diff --git a/rmxp-types/src/parameter_type.rs b/rmxp-types/src/helpers/parameter_type.rs similarity index 98% rename from rmxp-types/src/parameter_type.rs rename to rmxp-types/src/helpers/parameter_type.rs index e64dd672..f242ba5c 100644 --- a/rmxp-types/src/parameter_type.rs +++ b/rmxp-types/src/helpers/parameter_type.rs @@ -23,10 +23,9 @@ // Program grant you additional permission to convey the resulting work. use crate::rgss_structs::{Color, Tone}; -use crate::rpg::{AudioFile, MoveCommand, MoveRoute}; -use serde::{Deserialize, Serialize}; +use crate::shared::{AudioFile, MoveCommand, MoveRoute}; -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Default)] #[allow(missing_docs)] #[serde(from = "alox_48::Value")] #[serde(into = "alox_48::Value")] diff --git a/rmxp-types/src/lib.rs b/rmxp-types/src/lib.rs index 8e68caf3..c694fe45 100644 --- a/rmxp-types/src/lib.rs +++ b/rmxp-types/src/lib.rs @@ -1,12 +1,41 @@ #![feature(min_specialization)] +#![allow(non_upper_case_globals)] + +// Editor specific types +pub mod rmxp; + +// Shared structs with the same layout +mod shared; + +mod option_vec; -mod nil_padded; -mod parameter_type; mod rgss_structs; -pub mod rmxp_structs; +mod helpers; -pub use nil_padded::NilPadded; -pub use parameter_type::ParameterType; +pub use helpers::*; +pub use option_vec::OptionVec; pub use rgss_structs::{Color, Table1, Table2, Table3, Tone}; -pub use rmxp_structs as rpg; + +pub mod rpg { + pub use crate::rmxp::*; + pub use crate::shared::*; + + pub type Actors = Vec; + pub type Animations = Vec; + pub type Armors = Vec; + pub type Classes = Vec; + pub type CommonEvents = Vec; + pub type Enemies = Vec; + pub type Items = Vec; + pub type MapInfos = std::collections::HashMap; + pub type Skills = Vec; + pub type States = Vec; + pub type Tilesets = Vec; + pub type Troops = Vec; + pub type Weapons = Vec; +} + +pub use shared::BlendMode; + +pub type Path = Option; diff --git a/rmxp-types/src/nil_padded.rs b/rmxp-types/src/nil_padded.rs deleted file mode 100644 index 45559c0c..00000000 --- a/rmxp-types/src/nil_padded.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -// Copyright (C) 2023 Lily Lyons -// -// This file is part of Luminol. -// -// Luminol is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Luminol is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Luminol. If not, see . -// -// Additional permission under GNU GPL version 3 section 7 -// -// If you modify this Program, or any covered work, by linking or combining -// it with Steamworks API by Valve Corporation, containing parts covered by -// terms of the Steamworks API by Valve Corporation, the licensors of this -// Program grant you additional permission to convey the resulting work. - -/// An array that is serialized and deserialized as padded with a None element. -#[derive(Debug, Clone)] -pub struct NilPadded(pub Vec); - -impl<'de, T> serde::Deserialize<'de> for NilPadded -where - T: serde::Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct Visitor { - _marker: core::marker::PhantomData, - } - - impl<'de, T> serde::de::Visitor<'de> for Visitor - where - T: serde::Deserialize<'de>, - { - type Value = NilPadded; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("a nil padded array") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - use serde::de::Error; - - let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0)); - - if let Some(v) = seq.next_element::>()? { - if v.is_some() { - return Err(A::Error::custom("the first element was not nil")); - } - } - - while let Some(ele) = seq.next_element::()? { - values.push(ele); - } - - Ok(values.into()) - } - } - - deserializer.deserialize_seq(Visitor { - _marker: core::marker::PhantomData, - }) - } -} - -impl serde::Serialize for NilPadded -where - T: serde::Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeSeq; - - let mut seq = serializer.serialize_seq(Some(self.len() + 1))?; - seq.serialize_element(&None::)?; - - for v in self.iter() { - seq.serialize_element(v)?; - } - - seq.end() - } -} - -impl Deref for NilPadded { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for NilPadded { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for NilPadded { - fn default() -> Self { - Self(vec![]) - } -} - -impl From>> for NilPadded { - fn from(value: Vec>) -> Self { - let mut iter = value.into_iter(); - - assert!( - iter.next() - .expect("there should be at least one element") - .is_none(), - "the array should be padded with nil at the first index" - ); - Self(iter.flatten().collect()) - } -} - -impl From> for NilPadded { - fn from(value: Vec) -> Self { - Self(value) - } -} diff --git a/rmxp-types/src/option_vec.rs b/rmxp-types/src/option_vec.rs new file mode 100644 index 00000000..9f22d188 --- /dev/null +++ b/rmxp-types/src/option_vec.rs @@ -0,0 +1,250 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use std::ops::{Index, IndexMut}; + +use serde::ser::SerializeMap; + +#[derive(Debug, Clone, PartialEq, Eq)] +/// A vector that can contain unused indices. +pub struct OptionVec { + vec: Vec>, + num_values: usize, +} + +pub struct Iter<'a, T> { + vec_iter: std::iter::Enumerate>>, +} + +pub struct IterMut<'a, T> { + vec_iter: std::iter::Enumerate>>, +} + +pub struct Visitor(std::marker::PhantomData); + +impl OptionVec { + /// Create a new OptionVec with no elements. + pub fn new() -> Self { + Self { + vec: Vec::new(), + num_values: 0, + } + } + + pub fn len(&self) -> usize { + self.vec.len() + } + + pub fn size(&self) -> usize { + self.num_values + } + + pub fn is_empty(&self) -> bool { + self.vec.is_empty() + } + + pub fn get(&self, index: usize) -> Option<&T> { + self.vec.get(index).and_then(|x| x.as_ref()) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.vec.get_mut(index).and_then(|x| x.as_mut()) + } + + pub fn capacity(&self) -> usize { + self.vec.capacity() + } + + pub fn reserve(&mut self, additional: usize) { + self.vec.reserve(additional); + } + + pub fn iter(&self) -> Iter<'_, T> { + self.into_iter() + } + + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + self.into_iter() + } + + /// Write the element at the given index. + /// If there is already an element at the given index, it will be overwritten. + /// If there isn't, a new element will be added at that index. + pub fn insert(&mut self, index: usize, element: T) { + if index >= self.len() { + let additional = index - self.len() + 1; + self.reserve(additional); + self.vec + .extend(std::iter::repeat_with(|| None).take(additional)); + } + if self.vec[index].is_none() { + self.num_values += 1; + } + self.vec[index] = Some(element); + } + + /// Remove the element at the given index. + /// If the OptionVec is not big enough to contain this index, this will throw an error. + /// If there isn't an element at that index, this will throw an error. + pub fn try_remove(&mut self, index: usize) -> Result<(), String> { + if index >= self.len() { + Err(String::from("index out of bounds")) + } else if self.vec[index].is_none() { + Err(String::from("index not found")) + } else { + self.num_values -= 1; + self.vec[index] = None; + Ok(()) + } + } + + /// Remove the element at the given index. + /// If the OptionVec is not big enough to contain this index, this will panic. + /// If there isn't an element at that index, this will panic. + pub fn remove(&mut self, index: usize) { + self.try_remove(index).unwrap() + } +} + +impl Default for OptionVec { + fn default() -> Self { + OptionVec::new() + } +} + +impl FromIterator<(usize, T)> for OptionVec { + fn from_iter>(iterable: I) -> Self { + let mut vec = Vec::new(); + let mut num_values = 0; + for (i, v) in iterable.into_iter() { + if i >= vec.len() { + let additional = i - vec.len() + 1; + vec.reserve(additional); + vec.extend(std::iter::repeat_with(|| None).take(additional)); + } + if vec[i].is_none() { + num_values += 1; + } + vec[i] = Some(v); + } + Self { vec, num_values } + } +} + +impl Index for OptionVec { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + self.get(index).expect("index not found") + } +} + +impl IndexMut for OptionVec { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).expect("index not found") + } +} + +impl<'a, T> IntoIterator for &'a OptionVec { + type Item = (usize, &'a T); + type IntoIter = Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter { + vec_iter: self.vec.iter().enumerate(), + } + } +} + +impl<'a, T> IntoIterator for &'a mut OptionVec { + type Item = (usize, &'a mut T); + type IntoIter = IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter { + vec_iter: self.vec.iter_mut().enumerate(), + } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = (usize, &'a T); + fn next(&mut self) -> Option { + for (index, element) in &mut self.vec_iter { + if let Some(element) = element { + return Some((index, element)); + } + } + None + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = (usize, &'a mut T); + fn next(&mut self) -> Option { + for (index, element) in &mut self.vec_iter { + if let Some(element) = element { + return Some((index, element)); + } + } + None + } +} + +impl<'de, T> serde::de::Visitor<'de> for Visitor +where + T: serde::Deserialize<'de>, +{ + type Value = OptionVec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a key-value mapping") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + std::iter::from_fn(|| map.next_entry().transpose()).collect() + } +} + +impl<'de, T> serde::Deserialize<'de> for OptionVec +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(Visitor(std::marker::PhantomData)) + } +} + +impl serde::Serialize for OptionVec +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut ser = serializer.serialize_map(Some(self.size()))?; + for (index, element) in self { + ser.serialize_key(&index)?; + ser.serialize_value(element)?; + } + ser.end() + } +} diff --git a/rmxp-types/src/rgss_structs.rs b/rmxp-types/src/rgss_structs.rs index bf8812dc..e9e187d4 100644 --- a/rmxp-types/src/rgss_structs.rs +++ b/rmxp-types/src/rgss_structs.rs @@ -158,8 +158,12 @@ impl Table1 { self.data.is_empty() } - /// Return an iterator over all the elements in the table. - pub fn iter(&self) -> Iter<'_, i16> { + pub fn resize(&mut self, xsize: usize) { + self.data.resize(xsize, 0); + self.xsize = xsize; + } + + pub fn iter(&self) -> impl Iterator { self.data.iter() } } @@ -261,8 +265,22 @@ impl Table2 { self.data.is_empty() } - /// Return an iterator over all the elements in the table. - pub fn iter(&self) -> Iter<'_, i16> { + pub fn resize(&mut self, xsize: usize, ysize: usize) { + let mut new_data = vec![0; xsize * ysize]; + + for y in 0..self.ysize.min(ysize) { + for x in 0..self.xsize.min(xsize) { + new_data[xsize * y + x] = self[(x, y)] + } + } + + self.xsize = xsize; + self.ysize = ysize; + + self.data = new_data; + } + + pub fn iter(&self) -> impl Iterator { self.data.iter() } } @@ -335,8 +353,6 @@ impl From for alox_48::Userdata { } } -use std::slice::Iter; - impl Table3 { /// Create a new 3D table with a width of xsize, a height of ysize, and a depth of zsize. #[must_use] @@ -349,6 +365,17 @@ impl Table3 { } } + #[must_use] + pub fn new_data(xsize: usize, ysize: usize, zsize: usize, data: Vec) -> Self { + assert_eq!(xsize * ysize * zsize, data.len()); + Self { + xsize, + ysize, + zsize, + data, + } + } + /// Width of the table. #[must_use] pub fn xsize(&self) -> usize { @@ -379,8 +406,26 @@ impl Table3 { self.data.is_empty() } - /// Return an iterator over all the elements in the table. - pub fn iter(&self) -> Iter<'_, i16> { + pub fn resize(&mut self, xsize: usize, ysize: usize, zsize: usize) { + let mut new_data = vec![0; xsize * ysize]; + + // A naive for loop like this is optimized to a handful of memcpys. + for z in 0..self.zsize.min(zsize) { + for y in 0..self.ysize.min(ysize) { + for x in 0..self.xsize.min(xsize) { + new_data[(xsize * ysize * z) + (xsize * y) + x] = self[(x, y, z)] + } + } + } + + self.xsize = xsize; + self.ysize = ysize; + self.zsize = zsize; + + self.data = new_data; + } + + pub fn iter(&self) -> impl Iterator { self.data.iter() } } @@ -389,12 +434,12 @@ impl Index<(usize, usize, usize)> for Table3 { type Output = i16; fn index(&self, index: (usize, usize, usize)) -> &Self::Output { - &self.data[index.0 + (index.1 * self.xsize) + (index.2 * self.ysize)] + &self.data[index.0 + self.xsize * (index.1 + self.ysize * index.2)] } } impl IndexMut<(usize, usize, usize)> for Table3 { fn index_mut(&mut self, index: (usize, usize, usize)) -> &mut Self::Output { - &mut self.data[index.0 + (index.1 * self.xsize) + (index.2 * self.ysize)] + &mut self.data[index.0 + self.xsize * (index.1 + self.ysize * index.2)] } } diff --git a/rmxp-types/src/rmxp/actor.rs b/rmxp-types/src/rmxp/actor.rs new file mode 100644 index 00000000..ede11fc5 --- /dev/null +++ b/rmxp-types/src/rmxp/actor.rs @@ -0,0 +1,53 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{id, optional_id, optional_path, Path, Table2}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Actor")] +pub struct Actor { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "id")] + pub class_id: usize, + pub initial_level: i32, + pub final_level: i32, + pub exp_basis: i32, + pub exp_inflation: i32, + #[serde(with = "optional_path")] + pub character_name: Path, + pub character_hue: i32, + #[serde(with = "optional_path")] + pub battler_name: Path, + pub battler_hue: i32, + pub parameters: Table2, + #[serde(with = "optional_id")] + pub weapon_id: Option, + #[serde(with = "optional_id")] + pub armor1_id: Option, + #[serde(with = "optional_id")] + pub armor2_id: Option, + #[serde(with = "optional_id")] + pub armor3_id: Option, + #[serde(with = "optional_id")] + pub armor4_id: Option, + pub weapon_fix: bool, + pub armor1_fix: bool, + pub armor2_fix: bool, + pub armor3_fix: bool, + pub armor4_fix: bool, +} diff --git a/rmxp-types/src/rmxp/animation.rs b/rmxp-types/src/rmxp/animation.rs new file mode 100644 index 00000000..607b02e7 --- /dev/null +++ b/rmxp-types/src/rmxp/animation.rs @@ -0,0 +1,69 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{id, optional_path, rpg::AudioFile, Color, Path, Table2}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Animation")] +pub struct Animation { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_path")] + pub animation_name: Path, + pub animation_hue: i32, + pub position: Position, + pub frame_max: i32, + pub frames: Vec, + pub timings: Vec, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Animation::Timing")] +pub struct Timing { + pub frame: i32, + pub se: AudioFile, + pub flash_scope: i32, + pub flash_color: Color, + pub flash_duration: i32, + pub condition: i32, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Animation::Frame")] +pub struct Frame { + pub cell_max: i32, + pub cell_data: Table2, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Position { + Top = 0, + #[default] + Middle = 1, + Bottom = 2, + Screen = 3, +} diff --git a/rmxp-types/src/rmxp/armor.rs b/rmxp-types/src/rmxp/armor.rs new file mode 100644 index 00000000..a1f86e1e --- /dev/null +++ b/rmxp-types/src/rmxp/armor.rs @@ -0,0 +1,63 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{id, id_vec, optional_id, optional_path, Path}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Armor")] +pub struct Armor { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_path")] + pub icon_name: Path, + pub description: String, + pub kind: Kind, + #[serde(with = "optional_id")] + pub auto_state_id: Option, + pub price: i32, + pub pdef: i32, + pub mdef: i32, + pub eva: i32, + pub str_plus: i32, + pub dex_plus: i32, + pub agi_plus: i32, + pub int_plus: i32, + #[serde(with = "id_vec")] + pub guard_element_set: Vec, + #[serde(with = "id_vec")] + pub guard_state_set: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Kind { + #[default] + Shield = 0, + Helmet = 1, + #[strum(to_string = "Body Armor")] + BodyArmor = 2, + Accessory = 3, +} diff --git a/rmxp-types/src/rmxp/class.rs b/rmxp-types/src/rmxp/class.rs new file mode 100644 index 00000000..ea457f6f --- /dev/null +++ b/rmxp-types/src/rmxp/class.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::{id, id_vec, Table1}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Class")] +pub struct Class { + #[serde(with = "id")] + pub id: usize, + pub name: String, + pub position: Position, + #[serde(with = "id_vec")] + pub weapon_set: Vec, + #[serde(with = "id_vec")] + pub armor_set: Vec, + pub element_ranks: Table1, + pub state_ranks: Table1, + pub learnings: Vec, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Class::Learning")] +pub struct Learning { + pub level: i32, + #[serde(with = "id")] + pub skill_id: usize, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Position { + #[default] + Front = 0, + Middle = 1, + Rear = 2, +} diff --git a/rmxp-types/src/rmxp/enemy.rs b/rmxp-types/src/rmxp/enemy.rs new file mode 100644 index 00000000..6b2566cf --- /dev/null +++ b/rmxp-types/src/rmxp/enemy.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::{id, optional_id, optional_path, Path, Table1}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Enemy")] +pub struct Enemy { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_path")] + pub battler_name: Path, + pub battler_hue: i32, + pub maxhp: i32, + pub maxsp: i32, + pub str: i32, + pub dex: i32, + pub agi: i32, + pub int: i32, + pub atk: i32, + pub pdef: i32, + pub mdef: i32, + pub eva: i32, + #[serde(with = "optional_id")] + pub animation1_id: Option, + #[serde(with = "optional_id")] + pub animation2_id: Option, + pub element_ranks: Table1, + pub state_ranks: Table1, + pub actions: Vec, + pub exp: i32, + // FIXME: make optional + pub gold: i32, + #[serde(with = "optional_id")] + pub item_id: Option, + #[serde(with = "optional_id")] + pub weapon_id: Option, + #[serde(with = "optional_id")] + pub armor_id: Option, + pub treasure_prob: i32, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Enemy::Action")] +pub struct Action { + pub kind: i32, + pub basic: i32, + #[serde(with = "optional_id")] + pub skill_id: Option, + pub condition_turn_a: i32, + pub condition_turn_b: i32, + pub condition_hp: i32, + pub condition_level: i32, + #[serde(with = "optional_id")] + pub condition_switch_id: Option, + pub rating: i32, +} diff --git a/rmxp-types/src/rmxp/item.rs b/rmxp-types/src/rmxp/item.rs new file mode 100644 index 00000000..3ef8f634 --- /dev/null +++ b/rmxp-types/src/rmxp/item.rs @@ -0,0 +1,133 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::{id, id_vec, optional_id, optional_path, rpg::AudioFile, Path}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(rename = "RPG::Item")] +pub struct Item { + #[serde(with = "id")] + pub id: usize, + pub name: String, + pub icon_name: String, + pub description: String, + pub scope: Scope, + pub occasion: Occasion, + #[serde(with = "optional_id")] + pub animation1_id: Option, + #[serde(with = "optional_id")] + pub animation2_id: Option, + pub menu_se: AudioFile, + #[serde(with = "optional_id")] + pub common_event_id: Option, + pub price: i32, + pub consumable: bool, + pub parameter_type: ParameterType, + pub parameter_points: i32, + pub recover_hp_rate: i32, + pub recover_hp: i32, + // These fields are missing in rmxp data *sometimes*. + // Why? Who knows! + #[serde(default)] + pub recover_sp_rate: i32, + #[serde(default)] + pub recover_sp: i32, + pub hit: i32, + pub pdef_f: i32, + pub mdef_f: i32, + pub variance: i32, + #[serde(with = "id_vec")] + pub element_set: Vec, + #[serde(with = "id_vec")] + pub plus_state_set: Vec, + #[serde(with = "id_vec")] + pub minus_state_set: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Scope { + #[default] + None = 0, + #[strum(to_string = "One Enemy")] + OneEnemy = 1, + #[strum(to_string = "All Enemies")] + AllEnemies = 2, + #[strum(to_string = "One Ally")] + OneAlly = 3, + #[strum(to_string = "All Allies")] + AllAllies = 4, + #[strum(to_string = "One Ally (HP 0)")] + OneAllyHP0 = 5, + #[strum(to_string = "All Allies (HP 0)")] + AllAlliesHP0 = 6, + #[strum(to_string = "The User")] + User = 7, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Occasion { + #[default] + Always = 0, + #[strum(to_string = "Only in battle")] + OnlyBattle = 1, + #[strum(to_string = "Only from the menu")] + OnlyMenu = 2, + Never = 3, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum ParameterType { + #[default] + None = 0, + #[strum(to_string = "Max HP")] + MaxHP = 1, + #[strum(to_string = "Max SP")] + MaxSP = 2, + Str = 3, + Dex = 4, + Agi = 5, + Int = 6, +} diff --git a/rmxp-types/src/rmxp/map.rs b/rmxp-types/src/rmxp/map.rs new file mode 100644 index 00000000..6af02c07 --- /dev/null +++ b/rmxp-types/src/rmxp/map.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::rpg::{AudioFile, Event}; +use crate::{id, option_vec, Table3}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Map")] +pub struct Map { + #[serde(with = "id")] + pub tileset_id: usize, + pub width: usize, + pub height: usize, + pub autoplay_bgm: bool, + pub bgm: AudioFile, + pub autoplay_bgs: bool, + pub bgs: AudioFile, + pub encounter_list: Vec, + pub encounter_step: i32, + pub data: Table3, + pub events: option_vec::OptionVec, +} diff --git a/rmxp-types/src/rmxp/mod.rs b/rmxp-types/src/rmxp/mod.rs new file mode 100644 index 00000000..b4a5222f --- /dev/null +++ b/rmxp-types/src/rmxp/mod.rs @@ -0,0 +1,44 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub mod actor; +pub mod animation; +pub mod armor; +pub mod class; +pub mod enemy; +pub mod item; +pub mod map; +pub mod skill; +pub mod state; +pub mod system; +pub mod tileset; +pub mod troop; +pub mod weapon; + +pub use actor::Actor; +pub use animation::Animation; +pub use armor::Armor; +pub use class::Class; +pub use enemy::Enemy; +pub use item::Item; +pub use map::Map; +pub use skill::Skill; +pub use state::State; +pub use system::System; +pub use tileset::Tileset; +pub use troop::Troop; +pub use weapon::Weapon; diff --git a/rmxp-types/src/rmxp/skill.rs b/rmxp-types/src/rmxp/skill.rs new file mode 100644 index 00000000..191d81cd --- /dev/null +++ b/rmxp-types/src/rmxp/skill.rs @@ -0,0 +1,76 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::{id, id_vec, optional_id, optional_path, rpg::AudioFile, Path}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Skill")] +pub struct Skill { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_path")] + pub icon_name: Path, + pub description: String, + pub scope: i32, + pub occasion: Occasion, + #[serde(with = "optional_id")] + pub animation1_id: Option, + #[serde(with = "optional_id")] + pub animation2_id: Option, + pub menu_se: AudioFile, + #[serde(with = "optional_id")] + pub common_event_id: Option, + pub sp_cost: i32, + pub power: i32, + pub atk_f: i32, + pub eva_f: i32, + pub str_f: i32, + pub dex_f: i32, + pub agi_f: i32, + pub int_f: i32, + pub hit: i32, + pub pdef_f: i32, + pub mdef_f: i32, + pub variance: i32, + #[serde(with = "id_vec")] + pub element_set: Vec, + #[serde(with = "id_vec")] + pub plus_state_set: Vec, + #[serde(with = "id_vec")] + pub minus_state_set: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Occasion { + #[default] + Always = 0, + #[strum(to_string = "Only in battle")] + OnlyBattle = 1, + #[strum(to_string = "Only from the menu")] + OnlyMenu = 2, + Never = 3, +} diff --git a/rmxp-types/src/rmxp/state.rs b/rmxp-types/src/rmxp/state.rs new file mode 100644 index 00000000..9cecf9dd --- /dev/null +++ b/rmxp-types/src/rmxp/state.rs @@ -0,0 +1,79 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{id, id_vec, optional_id}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::State")] +pub struct State { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_id")] + pub animation_id: Option, + pub restriction: Restriction, + pub nonresistance: bool, + pub zero_hp: bool, + pub cant_get_exp: bool, + pub cant_evade: bool, + pub slip_damage: bool, + pub rating: i32, + pub hit_rate: i32, + pub maxhp_rate: i32, + pub maxsp_rate: i32, + pub str_rate: i32, + pub dex_rate: i32, + pub agi_rate: i32, + pub int_rate: i32, + pub atk_rate: i32, + pub pdef_rate: i32, + pub mdef_rate: i32, + pub eva: i32, + pub battle_only: bool, + pub hold_turn: i32, + pub auto_release_prob: i32, + pub shock_release_prob: i32, + #[serde(with = "id_vec")] + pub guard_element_set: Vec, + #[serde(with = "id_vec")] + pub plus_state_set: Vec, + #[serde(with = "id_vec")] + pub minus_state_set: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum Restriction { + #[default] + None = 0, + #[strum(to_string = "Can't use magic")] + NoMagic = 1, + #[strum(to_string = "Always attack enemies")] + AttackEnemies = 2, + #[strum(to_string = "Always attack allies")] + AttackAllies = 3, + #[strum(to_string = "Can't move")] + NoMove = 4, +} diff --git a/rmxp-types/src/rmxp/system.rs b/rmxp-types/src/rmxp/system.rs new file mode 100644 index 00000000..a9810c24 --- /dev/null +++ b/rmxp-types/src/rmxp/system.rs @@ -0,0 +1,116 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::{id, id_vec, nil_padded, optional_id, optional_path, rpg::AudioFile, Path}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(default)] // ??? rmxp??? +#[serde(rename = "RPG::System")] +pub struct System { + pub magic_number: i32, + #[serde(with = "id_vec")] + pub party_members: Vec, + pub elements: Vec, + #[serde(with = "nil_padded")] + pub switches: Vec, + #[serde(with = "nil_padded")] + pub variables: Vec, + + #[serde(with = "optional_path")] + pub windowskin_name: Path, + #[serde(with = "optional_path")] + pub title_name: Path, + #[serde(with = "optional_path")] + pub gameover_name: Path, + #[serde(with = "optional_path")] + pub battle_transition: Path, + pub title_bgm: AudioFile, + pub battle_bgm: AudioFile, + pub battle_end_me: AudioFile, + pub gameover_me: AudioFile, + pub cursor_se: AudioFile, + pub decision_se: AudioFile, + pub cancel_se: AudioFile, + pub buzzer_se: AudioFile, + pub equip_se: AudioFile, + pub shop_se: AudioFile, + pub save_se: AudioFile, + pub load_se: AudioFile, + pub battle_start_se: AudioFile, + pub escape_se: AudioFile, + pub actor_collapse_se: AudioFile, + pub enemy_collapse_se: AudioFile, + pub words: Words, + // #[serde(skip_deserializing)] + pub test_battlers: Vec, + #[serde(with = "optional_id")] + pub test_troop_id: Option, + #[serde(with = "id")] + pub start_map_id: usize, + pub start_x: i32, + pub start_y: i32, + #[serde(with = "optional_path")] + pub battleback_name: Path, + #[serde(with = "optional_path")] + pub battler_name: Path, + pub battler_hue: i32, + pub edit_map_id: usize, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::System::Words")] +#[serde(default)] +pub struct Words { + gold: String, + hp: String, + sp: String, + str: String, + dex: String, + agi: String, + int: String, + atk: String, + pdef: String, + mdef: String, + weapon: String, + armor1: String, + armor2: String, + armor3: String, + armor4: String, + attack: String, + skill: String, + guard: String, + item: String, + equip: String, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::System::TestBattler")] +pub struct TestBattler { + level: i32, + + #[serde(with = "id")] + actor_id: usize, + #[serde(with = "optional_id")] + weapon_id: Option, + #[serde(with = "optional_id")] + armor1_id: Option, + #[serde(with = "optional_id")] + armor2_id: Option, + #[serde(with = "optional_id")] + armor3_id: Option, + #[serde(with = "optional_id")] + armor4_id: Option, +} diff --git a/rmxp-types/src/rmxp/tileset.rs b/rmxp-types/src/rmxp/tileset.rs new file mode 100644 index 00000000..a1db5b8b --- /dev/null +++ b/rmxp-types/src/rmxp/tileset.rs @@ -0,0 +1,45 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use crate::{id, optional_path, BlendMode, Path, Table1}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Tileset")] +pub struct Tileset { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_path")] + pub tileset_name: Path, + pub autotile_names: Vec, + #[serde(with = "optional_path")] + pub panorama_name: Path, + pub panorama_hue: i32, + #[serde(with = "optional_path")] + pub fog_name: Path, + pub fog_hue: i32, + pub fog_opacity: i32, + pub fog_blend_type: BlendMode, + pub fog_zoom: i32, + pub fog_sx: i32, + pub fog_sy: i32, + #[serde(with = "optional_path")] + pub battleback_name: Path, + pub passages: Table1, + pub priorities: Table1, + pub terrain_tags: Table1, +} diff --git a/rmxp-types/src/rmxp/troop.rs b/rmxp-types/src/rmxp/troop.rs new file mode 100644 index 00000000..5fcdbb3c --- /dev/null +++ b/rmxp-types/src/rmxp/troop.rs @@ -0,0 +1,64 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{id, optional_id, rpg::EventCommand}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Troop")] +pub struct Troop { + #[serde(with = "id")] + pub id: usize, + pub name: String, + pub members: Vec, + pub pages: Vec, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Troop::Member")] +pub struct Member { + #[serde(with = "id")] + pub enemy_id: usize, + pub x: i32, + pub y: i32, + pub hidden: bool, + pub immortal: bool, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Troop::Page")] +pub struct Page { + pub condition: Condition, + pub span: i32, + pub list: Vec, +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Troop::Page::Condition")] +pub struct Condition { + pub turn_valid: bool, + pub enemy_valid: bool, + pub actor_valid: bool, + pub switch_valid: bool, + pub turn_a: i32, + pub turn_b: i32, + pub enemy_index: usize, + pub enemy_hp: i32, + #[serde(with = "optional_id")] + pub actor_id: Option, + pub actor_hp: i32, + #[serde(with = "optional_id")] + pub switch_id: Option, +} diff --git a/rmxp-types/src/rmxp/weapon.rs b/rmxp-types/src/rmxp/weapon.rs new file mode 100644 index 00000000..5814942b --- /dev/null +++ b/rmxp-types/src/rmxp/weapon.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::{id, id_vec, optional_id, optional_path, rpg::AudioFile, Path}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename = "RPG::Weapon")] +pub struct Weapon { + #[serde(with = "id")] + pub id: usize, + pub name: String, + #[serde(with = "optional_path")] + pub icon_name: Path, + pub description: String, + #[serde(with = "optional_id")] + pub animation1_id: Option, + #[serde(with = "optional_id")] + pub animation2_id: Option, + pub price: i32, + pub atk: i32, + pub pdef: i32, + pub mdef: i32, + pub str_plus: i32, + pub dex_plus: i32, + pub agi_plus: i32, + pub int_plus: i32, + #[serde(with = "id_vec")] + pub element_set: Vec, + #[serde(with = "id_vec")] + pub plus_state_set: Vec, + #[serde(with = "id_vec")] + pub minus_state_set: Vec, +} diff --git a/rmxp-types/src/rmxp_structs.rs b/rmxp-types/src/rmxp_structs.rs deleted file mode 100644 index 2c9a557b..00000000 --- a/rmxp-types/src/rmxp_structs.rs +++ /dev/null @@ -1,881 +0,0 @@ -#![allow(dead_code, missing_docs)] -#![allow(clippy::struct_excessive_bools)] - -use std::fmt::Display; - -use num_derive::FromPrimitive; -use slab::Slab; -use strum::EnumIter; - -use crate::nil_padded::NilPadded; -use crate::rgss_structs::{Color, Table1, Table2, Table3}; -use crate::ParameterType; -use serde::{Deserialize, Serialize}; - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Map")] -pub struct Map { - pub tileset_id: i32, - pub width: usize, - pub height: usize, - pub autoplay_bgm: bool, - pub bgm: AudioFile, - pub autoplay_bgs: bool, - pub bgs: AudioFile, - pub encounter_list: Vec, - pub encounter_step: i32, - pub data: Table3, - pub events: Slab, - - #[serde(skip)] - /// (direction: i32, start_pos: Pos2, route: MoveRoute) - pub preview_move_route: Option<(i32, MoveRoute)>, -} - -#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(rename = "RPG::MapInfo")] -pub struct MapInfo { - pub name: String, - pub parent_id: i32, - pub order: i32, - pub expanded: bool, - pub scroll_x: i32, - pub scroll_y: i32, -} - -impl PartialOrd for MapInfo { - fn partial_cmp(&self, other: &Self) -> Option { - self.order.partial_cmp(&other.order) - } -} - -impl Ord for MapInfo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.order.cmp(&other.order) - } -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(rename = "RPG::Event::Page::Condition")] -pub struct EventCondition { - pub switch1_valid: bool, - pub switch2_valid: bool, - pub variable_valid: bool, - pub self_switch_valid: bool, - pub switch1_id: usize, - pub switch2_id: usize, - pub variable_id: usize, - pub variable_value: i32, - pub self_switch_ch: String, -} - -impl Default for EventCondition { - fn default() -> Self { - Self { - switch1_valid: false, - switch2_valid: false, - variable_valid: false, - self_switch_valid: false, - switch1_id: 1, - switch2_id: 1, - variable_id: 1, - variable_value: 0, - self_switch_ch: "A".to_string(), - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(rename = "RPG::Event::Page::Graphic")] -pub struct Graphic { - pub tile_id: i32, - pub character_name: String, - pub character_hue: i32, - pub direction: i32, - pub pattern: i32, - pub opacity: i32, - pub blend_type: i32, -} - -impl Default for Graphic { - fn default() -> Self { - Self { - tile_id: 0, - character_name: String::new(), - character_hue: 0, - direction: 2, - pattern: 0, - opacity: 255, - blend_type: 0, - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(rename = "RPG::Event::Page")] -pub struct EventPage { - pub condition: EventCondition, - pub graphic: Graphic, - pub move_type: usize, - pub move_speed: usize, - pub move_frequency: usize, - pub move_route: MoveRoute, - pub walk_anime: bool, - pub step_anime: bool, - pub direction_fix: bool, - pub through: bool, - pub always_on_top: bool, - pub trigger: i32, - pub list: Vec, -} - -impl Default for EventPage { - fn default() -> Self { - Self { - condition: EventCondition::default(), - graphic: Graphic::default(), - move_type: 0, - move_speed: 3, - move_frequency: 3, - move_route: MoveRoute::default(), - walk_anime: true, - step_anime: false, - direction_fix: false, - through: false, - always_on_top: false, - trigger: 0, - list: vec![], - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(rename = "RPG::Event")] -pub struct Event { - pub id: usize, - pub name: String, - pub x: i32, - pub y: i32, - pub pages: Vec, -} - -impl Event { - #[must_use] - pub fn new(x: i32, y: i32, id: usize) -> Self { - Self { - id, - name: format!("EV{id:0>3}"), - x, - y, - pages: vec![EventPage::default()], - } - } -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq)] -#[serde(rename = "RPG::MoveRoute")] -pub struct MoveRoute { - pub repeat: bool, - pub skippable: bool, - pub list: Vec, -} - -impl From for MoveRoute { - fn from(obj: alox_48::Object) -> Self { - MoveRoute { - repeat: obj.fields["repeat"].clone().into_bool().unwrap(), - skippable: obj.fields["skippable"].clone().into_bool().unwrap(), - list: obj.fields["list"] - .clone() - .into_array() - .unwrap() - .into_iter() - .map(|obj| { - let obj = obj.into_object().unwrap(); - obj.into() - }) - .collect(), - } - } -} - -impl From for alox_48::Object { - fn from(value: MoveRoute) -> Self { - let mut fields = alox_48::value::RbFields::with_capacity(3); - fields.insert("repeat".into(), alox_48::Value::Bool(value.repeat)); - fields.insert("skippable".into(), alox_48::Value::Bool(value.skippable)); - fields.insert( - "list".into(), - alox_48::Value::Array( - value - .list - .into_iter() - .map(Into::into) - .map(alox_48::Value::Object) - .collect(), - ), - ); - - alox_48::Object { - class: "RPG::MoveRoute".into(), - fields, - } - } -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Actor")] -pub struct Actor { - pub id: i32, - pub name: String, - pub class_id: i32, - pub initial_level: i32, - pub final_level: i32, - pub exp_basis: i32, - pub exp_inflation: i32, - pub character_name: String, - pub character_hue: i32, - pub battler_name: String, - pub battler_hue: i32, - pub parameters: Table2, - pub weapon_id: i32, - pub armor1_id: i32, - pub armor2_id: i32, - pub armor3_id: i32, - pub armor4_id: i32, - pub weapon_fix: bool, - pub armor1_fix: bool, - pub armor2_fix: bool, - pub armor3_fix: bool, - pub armor4_fix: bool, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Class::Learning")] -pub struct Learning { - pub level: i32, - pub skill_id: i32, -} -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Class")] -pub struct Class { - pub id: i32, - pub name: String, - pub position: i32, - pub weapon_set: Vec, - pub armor_set: Vec, - pub element_ranks: Table1, - pub state_ranks: Table1, - pub learnings: Vec, -} - -// FIXME: I don't use the battle system, so I'm unsure what some of these types *should* be. -// I plan to support the battle system but that comes after everything else. -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Skill")] -pub struct Skill { - pub id: i32, - pub name: String, - pub icon_name: String, - pub description: String, - pub scope: i32, - pub occasion: i32, - pub animation1_id: i32, - pub animation2_id: i32, - pub menu_se: AudioFile, - pub common_event_id: i32, - pub sp_cost: i32, - pub power: i32, - pub atk_f: i32, - pub eva_f: i32, - pub str_f: i32, - pub dex_f: i32, - pub agi_f: i32, - pub int_f: i32, - pub hit: i32, - pub pdef_f: i32, - pub mdef_f: i32, - pub variance: i32, - pub element_set: Vec, - pub plus_state_set: Vec, - pub minus_state_set: Vec, -} - -#[derive(Debug, EnumIter, FromPrimitive)] -pub enum ItemScope { - None, - OneEnemy, - AllEnemies, - OneAlly, - AllAllies, - OneAllyHP0, - AllAlliesHP0, - TheUser, -} -impl Display for ItemScope { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use ItemScope::{ - AllAllies, AllAlliesHP0, AllEnemies, None, OneAlly, OneAllyHP0, OneEnemy, TheUser, - }; - - write!( - f, - "{}", - match self { - None => "None", - OneEnemy => "One Enemy", - AllEnemies => "All Enemies", - OneAlly => "One Ally", - AllAllies => "All Allies", - OneAllyHP0 => "One Ally (HP 0)", - AllAlliesHP0 => "All Allies (HP 0)", - TheUser => "The User", - } - ) - } -} - -#[derive(Debug, FromPrimitive, EnumIter)] -pub enum ItemOccasion { - Always, - OnlyInBattle, - OnlyFromTheMenu, - Never, -} -impl Display for ItemOccasion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use ItemOccasion::*; - - write!( - f, - "{}", - match self { - Always => "Always", - OnlyInBattle => "Only in Battle", - OnlyFromTheMenu => "Only from the Menu", - Never => "Never", - }, - ) - } -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone)] -#[serde(rename = "RPG::Item")] -pub struct Item { - pub id: i32, - pub name: String, - pub icon_name: String, - pub description: String, - pub scope: i32, - pub occasion: i32, - pub animation1_id: i32, - pub animation2_id: i32, - pub menu_se: AudioFile, - pub common_event_id: i32, - pub price: i32, - pub consumable: bool, - pub parameter_type: i32, - pub parameter_points: i32, - pub recover_hp_rate: i32, - pub recover_hp: i32, - // These fields are missing in rmxp data *sometimes*. - // Why? Who knows! - #[serde(default)] - pub recover_sp_rate: i32, - #[serde(default)] - pub recover_sp: i32, - pub hit: i32, - pub pdef_f: i32, - pub mdef_f: i32, - pub variance: i32, - pub element_set: Vec, - pub plus_state_set: Vec, - pub minus_state_set: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Weapon")] -pub struct Weapon { - pub id: i32, - pub name: String, - pub icon_name: String, - pub description: String, - pub animation1_id: i32, - pub animation2_id: i32, - pub price: i32, - pub atk: i32, - pub pdef: i32, - pub mdef: i32, - pub str_plus: i32, - pub dex_plus: i32, - pub agi_plus: i32, - pub int_plus: i32, - pub element_set: Vec, - pub plus_state_set: Vec, - pub minus_state_set: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Armor")] -pub struct Armor { - pub id: i32, - pub name: String, - pub icon_name: String, - pub description: String, - pub kind: i32, - pub auto_state_id: i32, - pub price: i32, - pub pdef: i32, - pub mdef: i32, - pub eva: i32, - pub str_plus: i32, - pub dex_plus: i32, - pub agi_plus: i32, - pub int_plus: i32, - pub guard_element_set: Vec, - pub guard_state_set: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Enemy::Action")] -pub struct Action { - pub kind: i32, - pub basic: i32, - pub skill_id: i32, - pub condition_turn_a: i32, - pub condition_turn_b: i32, - pub condition_hp: i32, - pub condition_level: i32, - pub condition_switch_id: i32, - pub rating: i32, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Enemy")] -pub struct Enemy { - pub id: i32, - pub name: String, - pub battler_name: String, - pub battler_hue: i32, - pub maxhp: i32, - pub maxsp: i32, - pub str: i32, - pub dex: i32, - pub agi: i32, - pub int: i32, - pub atk: i32, - pub pdef: i32, - pub mdef: i32, - pub eva: i32, - pub animation1_id: i32, - pub animation2_id: i32, - pub element_ranks: Table1, - pub state_ranks: Table1, - pub actions: Vec, - pub exp: i32, - pub gold: i32, - pub item_id: i32, - pub weapon_id: i32, - pub armor_id: i32, - pub treasure_prob: i32, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Troop::Member")] -pub struct Member { - pub enemy_id: i32, - pub x: i32, - pub y: i32, - pub hidden: bool, - pub immortal: bool, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Troop::Page::Condition")] -pub struct TroopCondition { - pub turn_valid: bool, - pub enemy_valid: bool, - pub actor_valid: bool, - pub switch_valid: bool, - pub turn_a: i32, - pub turn_b: i32, - pub enemy_index: i32, - pub enemy_hp: i32, - pub actor_id: i32, - pub actor_hp: i32, - pub switch_id: i32, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Troop::Page")] -pub struct TroopPage { - pub condition: TroopCondition, - pub span: i32, - pub list: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Troop")] -pub struct Troop { - pub id: i32, - pub name: String, - pub members: Vec, - pub pages: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::State")] -pub struct State { - pub id: i32, - pub name: String, - pub animation_id: i32, - pub restriction: i32, - pub nonresistance: bool, - pub zero_hp: bool, - pub cant_get_exp: bool, - pub cant_evade: bool, - pub slip_damage: bool, - pub rating: i32, - pub hit_rate: i32, - pub maxhp_rate: i32, - pub maxsp_rate: i32, - pub str_rate: i32, - pub dex_rate: i32, - pub agi_rate: i32, - pub int_rate: i32, - pub atk_rate: i32, - pub pdef_rate: i32, - pub mdef_rate: i32, - pub eva: i32, - pub battle_only: bool, - pub hold_turn: i32, - pub auto_release_prob: i32, - pub shock_release_prob: i32, - pub guard_element_set: Vec, - pub plus_state_set: Vec, - pub minus_state_set: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Animation::Frame")] -pub struct Frame { - pub cell_max: i32, - pub cell_data: Table2, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Animation::Timing")] -pub struct Timing { - pub frame: i32, - pub se: AudioFile, - pub flash_scope: i32, - pub flash_color: Color, - pub flash_duration: i32, - pub condition: i32, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Animation")] -pub struct Animation { - pub id: i32, - pub name: String, - pub animation_name: String, - pub animation_hue: i32, - pub position: i32, - pub frame_max: i32, - pub frames: Vec, - pub timings: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::Tileset")] -pub struct Tileset { - pub id: i32, - pub name: String, - pub tileset_name: String, - pub autotile_names: Vec, - pub panorama_name: String, - pub panorama_hue: i32, - pub fog_name: String, - pub fog_hue: i32, - pub fog_opacity: i32, - pub fog_blend_type: i32, - pub fog_zoom: i32, - pub fog_sx: i32, - pub fog_sy: i32, - pub battleback_name: String, - pub passages: Table1, - pub priorities: Table1, - pub terrain_tags: Table1, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone)] -#[serde(rename = "RPG::CommonEvent")] -pub struct CommonEvent { - pub id: usize, - pub name: String, - pub trigger: usize, - pub switch_id: usize, - pub list: Vec, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::System::Words")] -#[serde(default)] -pub struct Words { - gold: String, - hp: String, - sp: String, - str: String, - dex: String, - agi: String, - int: String, - atk: String, - pdef: String, - mdef: String, - weapon: String, - armor1: String, - armor2: String, - armor3: String, - armor4: String, - attack: String, - skill: String, - guard: String, - item: String, - equip: String, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(rename = "RPG::System::TestBattler")] -pub struct TestBattler { - actor_id: i32, - level: i32, - weapon_id: i32, - armor1_id: i32, - armor2_id: i32, - armor3_id: i32, - armor4_id: i32, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(default)] // ??? rmxp??? -#[serde(rename = "RPG::System")] -pub struct System { - pub magic_number: i32, - pub party_members: Vec, - pub elements: Vec, - pub switches: NilPadded, - pub variables: NilPadded, - pub windowskin_name: String, - pub title_name: String, - pub gameover_name: String, - pub battle_transition: String, - pub title_bgm: AudioFile, - pub battle_bgm: AudioFile, - pub battle_end_me: AudioFile, - pub gameover_me: AudioFile, - pub cursor_se: AudioFile, - pub decision_se: AudioFile, - pub cancel_se: AudioFile, - pub buzzer_se: AudioFile, - pub equip_se: AudioFile, - pub shop_se: AudioFile, - pub save_se: AudioFile, - pub load_se: AudioFile, - pub battle_start_se: AudioFile, - pub escape_se: AudioFile, - pub actor_collapse_se: AudioFile, - pub enemy_collapse_se: AudioFile, - pub words: Words, - #[serde(skip_deserializing)] - pub test_battlers: Vec, - pub test_troop_id: i32, - pub start_map_id: i32, - pub start_x: i32, - pub start_y: i32, - pub battleback_name: String, - pub battler_name: String, - pub battler_hue: i32, - pub edit_map_id: i32, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq)] -#[serde(rename = "RPG::AudioFile")] -pub struct AudioFile { - pub name: String, - pub volume: u8, - pub pitch: u8, -} - -impl From for AudioFile { - fn from(obj: alox_48::Object) -> Self { - AudioFile { - name: obj.fields["name"] - .clone() - .into_string() - .unwrap() - .to_string() - .unwrap(), - volume: obj.fields["volume"].clone().into_integer().unwrap() as _, - pitch: obj.fields["pitch"].clone().into_integer().unwrap() as _, - } - } -} - -impl From for alox_48::Object { - fn from(a: AudioFile) -> Self { - let mut fields = alox_48::value::RbFields::with_capacity(3); - fields.insert("name".into(), a.name.into()); - fields.insert("volume".into(), alox_48::Value::Integer(a.volume as _)); - fields.insert("pitch".into(), alox_48::Value::Integer(a.pitch as _)); - - alox_48::Object { - class: "RPG::AudioFile".into(), - fields, - } - } -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -#[allow(missing_docs)] -#[serde(rename = "RPG::EventCommand")] -pub struct EventCommand { - pub code: u16, - pub indent: usize, - pub parameters: Vec, - - #[serde(default = "rand::random")] - #[serde(skip)] - pub guid: u16, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq)] -#[allow(missing_docs)] -#[serde(rename = "RPG::MoveCommand")] -pub struct MoveCommand { - pub code: u16, - pub parameters: Vec, - - #[serde(default = "rand::random")] - #[serde(skip)] - pub guid: u16, -} - -impl From for MoveCommand { - fn from(obj: alox_48::Object) -> Self { - MoveCommand { - code: obj.fields["code"].clone().into_integer().unwrap() as _, - parameters: obj.fields["parameters"] - .clone() - .into_array() - .unwrap() - .into_iter() - .map(Into::into) - .collect(), - - guid: rand::random(), - } - } -} - -impl From for alox_48::Object { - fn from(c: MoveCommand) -> Self { - let mut fields = alox_48::value::RbFields::with_capacity(2); - fields.insert("code".into(), alox_48::Value::Integer(c.code as _)); - fields.insert( - "parameters".into(), - alox_48::Value::Array(c.parameters.into_iter().map(Into::into).collect()), - ); - - alox_48::Object { - class: "RPG::MoveCommand".into(), - fields, - } - } -} - -// FIXME: add custom serialize impl -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct Script { - pub name: String, - pub script_text: String, -} - -impl<'de> serde::Deserialize<'de> for Script { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = Script; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("an array") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - use serde::de::Error; - use std::io::Read; - - let Some(_) = seq.next_element::()? else { - return Err(A::Error::missing_field("id")); - }; - - let Some(name) = seq.next_element()? else { - return Err(A::Error::missing_field("name")); - }; - - let Some(data) = seq.next_element::()? else { - return Err(A::Error::missing_field("data")); - }; - - let mut decoder = flate2::bufread::ZlibDecoder::new(data.data.as_slice()); - let mut script = String::new(); - decoder - .read_to_string(&mut script) - .map_err(A::Error::custom)?; - - Ok(Script { - name, - script_text: script, - }) - } - } - - deserializer.deserialize_any(Visitor) - } -} - -impl Serialize for Script { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::Error; - use serde::ser::SerializeSeq; - use std::io::Write; - - let mut seq = serializer.serialize_seq(Some(3))?; - - let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), Default::default()); - let data = encoder - .write_all(self.script_text.as_bytes()) - .and_then(|_| encoder.finish()) - .map_err(S::Error::custom)?; - - seq.serialize_element(&0usize)?; - seq.serialize_element(&self.name)?; - seq.serialize_element(&alox_48::RbString { - data, - ..Default::default() - })?; - - seq.end() - } -} diff --git a/rmxp-types/src/shared/audio_file.rs b/rmxp-types/src/shared/audio_file.rs new file mode 100644 index 00000000..46bbd4b5 --- /dev/null +++ b/rmxp-types/src/shared/audio_file.rs @@ -0,0 +1,64 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{optional_path, Path}; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] +#[serde(rename = "RPG::AudioFile")] +pub struct AudioFile { + #[serde(with = "optional_path")] + pub name: Path, + pub volume: u8, + pub pitch: u8, +} + +impl From for AudioFile { + fn from(obj: alox_48::Object) -> Self { + let name = obj.fields["name"] + .clone() + .into_string() + .unwrap() + .to_string() + .unwrap(); + let name = if name.is_empty() { + None + } else { + Some(name.into()) + }; + AudioFile { + name, + volume: obj.fields["volume"].clone().into_integer().unwrap() as _, + pitch: obj.fields["pitch"].clone().into_integer().unwrap() as _, + } + } +} + +impl From for alox_48::Object { + fn from(a: AudioFile) -> Self { + let mut fields = alox_48::value::RbFields::with_capacity(3); + fields.insert( + "name".into(), + a.name.map(camino::Utf8PathBuf::into_string).into(), + ); + fields.insert("volume".into(), alox_48::Value::Integer(a.volume as _)); + fields.insert("pitch".into(), alox_48::Value::Integer(a.pitch as _)); + + alox_48::Object { + class: "RPG::AudioFile".into(), + fields, + } + } +} diff --git a/rmxp-types/src/shared/event.rs b/rmxp-types/src/shared/event.rs new file mode 100644 index 00000000..3192ec2b --- /dev/null +++ b/rmxp-types/src/shared/event.rs @@ -0,0 +1,174 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::{id, optional_id, optional_path, rpg::MoveRoute, BlendMode, ParameterType, Path}; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(rename = "RPG::Event")] +pub struct Event { + // #[serde(with = "id")] + pub id: usize, + pub name: String, + pub x: i32, + pub y: i32, + pub pages: Vec, + + #[serde(skip)] + pub extra_data: EventExtraData, +} + +#[derive(Debug, Default, Clone)] +pub struct EventExtraData { + /// Whether or not the event editor for this event is open + pub is_editor_open: bool, +} + +impl Event { + #[must_use] + pub fn new(x: i32, y: i32, id: usize) -> Self { + Self { + id, + name: format!("EV{id:0>3}"), + x, + y, + pages: vec![EventPage::default()], + + extra_data: EventExtraData::default(), + } + } +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(rename = "RPG::CommonEvent")] +pub struct CommonEvent { + #[serde(with = "id")] + pub id: usize, + pub name: String, + pub trigger: usize, + pub switch_id: usize, + pub list: Vec, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(rename = "RPG::Event::Page")] +pub struct EventPage { + pub condition: EventCondition, + pub graphic: Graphic, + pub move_type: usize, + pub move_speed: usize, + pub move_frequency: usize, + pub move_route: MoveRoute, + pub walk_anime: bool, + pub step_anime: bool, + pub direction_fix: bool, + pub through: bool, + pub always_on_top: bool, + pub trigger: i32, + pub list: Vec, +} + +impl Default for EventPage { + fn default() -> Self { + Self { + condition: EventCondition::default(), + graphic: Graphic::default(), + move_type: 0, + move_speed: 3, + move_frequency: 3, + move_route: MoveRoute::default(), + walk_anime: true, + step_anime: false, + direction_fix: false, + through: false, + always_on_top: false, + trigger: 0, + list: vec![], + } + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(rename = "RPG::Event::Page::Graphic")] +pub struct Graphic { + #[serde(with = "optional_id")] + pub tile_id: Option, + #[serde(with = "optional_path")] + pub character_name: Path, + pub character_hue: i32, + pub direction: i32, + pub pattern: i32, + pub opacity: i32, + pub blend_type: BlendMode, +} + +impl Default for Graphic { + fn default() -> Self { + Self { + tile_id: None, + character_name: None, + character_hue: 0, + direction: 2, + pattern: 0, + opacity: 255, + blend_type: BlendMode::Normal, + } + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(rename = "RPG::Event::Page::Condition")] +pub struct EventCondition { + pub switch1_valid: bool, + pub switch2_valid: bool, + pub variable_valid: bool, + pub self_switch_valid: bool, + #[serde(with = "id")] + pub switch1_id: usize, + #[serde(with = "id")] + pub switch2_id: usize, + pub variable_id: usize, + pub variable_value: i32, + pub self_switch_ch: String, +} + +impl Default for EventCondition { + fn default() -> Self { + Self { + switch1_valid: false, + switch2_valid: false, + variable_valid: false, + self_switch_valid: false, + switch1_id: 0, + switch2_id: 0, + variable_id: 0, + variable_value: 0, + self_switch_ch: "A".to_string(), + } + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[allow(missing_docs)] +#[serde(rename = "RPG::EventCommand")] +pub struct EventCommand { + pub code: u16, + pub indent: usize, + pub parameters: Vec, + + #[serde(default = "rand::random")] + #[serde(skip)] + pub guid: u16, +} diff --git a/rmxp-types/src/shared/mapinfo.rs b/rmxp-types/src/shared/mapinfo.rs new file mode 100644 index 00000000..f001f0e4 --- /dev/null +++ b/rmxp-types/src/shared/mapinfo.rs @@ -0,0 +1,42 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)] +#[serde(rename = "RPG::MapInfo")] +pub struct MapInfo { + pub name: String, + // because mapinfos is stored in a hash, we dont actually need to modify values! this can just stay as a usize. + // it would be slightly more accurate to store this as an option, but no other values (off the top of my head) are like this. maybe event tile ids. + // I'll need to think on this a bit. + pub parent_id: usize, + pub order: i32, + pub expanded: bool, + pub scroll_x: i32, + pub scroll_y: i32, +} + +impl PartialOrd for MapInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MapInfo { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.order.cmp(&other.order) + } +} diff --git a/rmxp-types/src/shared/mod.rs b/rmxp-types/src/shared/mod.rs new file mode 100644 index 00000000..069bd4a8 --- /dev/null +++ b/rmxp-types/src/shared/mod.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +mod audio_file; +mod event; +mod mapinfo; +mod move_route; +mod script; + +pub use audio_file::*; +pub use event::*; +pub use mapinfo::*; +pub use move_route::*; +pub use script::*; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Default, Hash)] +#[derive( + num_enum::TryFromPrimitive, + num_enum::IntoPrimitive, + strum::Display, + strum::EnumIter +)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(u8)] +#[serde(into = "u8")] +#[serde(try_from = "u8")] +pub enum BlendMode { + #[default] + Normal = 0, + Add = 1, + Subtract = 2, +} diff --git a/rmxp-types/src/shared/move_route.rs b/rmxp-types/src/shared/move_route.rs new file mode 100644 index 00000000..97c732b2 --- /dev/null +++ b/rmxp-types/src/shared/move_route.rs @@ -0,0 +1,113 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::helpers::ParameterType; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] +#[serde(rename = "RPG::MoveRoute")] +pub struct MoveRoute { + pub repeat: bool, + pub skippable: bool, + pub list: Vec, +} + +impl From for MoveRoute { + fn from(obj: alox_48::Object) -> Self { + MoveRoute { + repeat: obj.fields["repeat"].clone().into_bool().unwrap(), + skippable: obj.fields["skippable"].clone().into_bool().unwrap(), + list: obj.fields["list"] + .clone() + .into_array() + .unwrap() + .into_iter() + .map(|obj| { + let obj = obj.into_object().unwrap(); + obj.into() + }) + .collect(), + } + } +} + +impl From for alox_48::Object { + fn from(value: MoveRoute) -> Self { + let mut fields = alox_48::value::RbFields::with_capacity(3); + fields.insert("repeat".into(), alox_48::Value::Bool(value.repeat)); + fields.insert("skippable".into(), alox_48::Value::Bool(value.skippable)); + fields.insert( + "list".into(), + alox_48::Value::Array( + value + .list + .into_iter() + .map(Into::into) + .map(alox_48::Value::Object) + .collect(), + ), + ); + + alox_48::Object { + class: "RPG::MoveRoute".into(), + fields, + } + } +} + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] +#[allow(missing_docs)] +#[serde(rename = "RPG::MoveCommand")] +pub struct MoveCommand { + pub code: u16, + pub parameters: Vec, + + #[serde(default = "rand::random")] + #[serde(skip)] + pub guid: u16, +} + +impl From for MoveCommand { + fn from(obj: alox_48::Object) -> Self { + MoveCommand { + code: obj.fields["code"].clone().into_integer().unwrap() as _, + parameters: obj.fields["parameters"] + .clone() + .into_array() + .unwrap() + .into_iter() + .map(Into::into) + .collect(), + + guid: rand::random(), + } + } +} + +impl From for alox_48::Object { + fn from(c: MoveCommand) -> Self { + let mut fields = alox_48::value::RbFields::with_capacity(2); + fields.insert("code".into(), alox_48::Value::Integer(c.code as _)); + fields.insert( + "parameters".into(), + alox_48::Value::Array(c.parameters.into_iter().map(Into::into).collect()), + ); + + alox_48::Object { + class: "RPG::MoveCommand".into(), + fields, + } + } +} diff --git a/rmxp-types/src/shared/script.rs b/rmxp-types/src/shared/script.rs new file mode 100644 index 00000000..6c3fb3db --- /dev/null +++ b/rmxp-types/src/shared/script.rs @@ -0,0 +1,101 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct Script { + pub name: String, + pub script_text: String, +} + +impl<'de> serde::Deserialize<'de> for Script { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Script; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("an array") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + use serde::de::Error; + use std::io::Read; + + let Some(_) = seq.next_element::()? else { + return Err(A::Error::missing_field("id")); + }; + + let Some(name) = seq.next_element()? else { + return Err(A::Error::missing_field("name")); + }; + + let Some(data) = seq.next_element::()? else { + return Err(A::Error::missing_field("data")); + }; + + let mut decoder = flate2::bufread::ZlibDecoder::new(data.data.as_slice()); + let mut script = String::new(); + decoder + .read_to_string(&mut script) + .map_err(A::Error::custom)?; + + Ok(Script { + name, + script_text: script, + }) + } + } + + deserializer.deserialize_any(Visitor) + } +} + +impl serde::Serialize for Script { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::Error; + use serde::ser::SerializeSeq; + use std::io::Write; + + let mut seq = serializer.serialize_seq(Some(3))?; + + let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), Default::default()); + let data = encoder + .write_all(self.script_text.as_bytes()) + .and_then(|_| encoder.finish()) + .map_err(S::Error::custom)?; + + seq.serialize_element(&0usize)?; + seq.serialize_element(&self.name)?; + seq.serialize_element(&alox_48::RbString { + data, + ..Default::default() + })?; + + seq.end() + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 44ad673b..e7d8dc01 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2023-04-21" +channel = "nightly-2023-09-12" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..d4fcd2a3 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +merge_derives = false diff --git a/src/audio/GMGSx.sf2 b/src/audio/GMGSx.sf2 new file mode 100644 index 00000000..5ebfefbb Binary files /dev/null and b/src/audio/GMGSx.sf2 differ diff --git a/src/audio/midi.rs b/src/audio/midi.rs new file mode 100644 index 00000000..0ebd8000 --- /dev/null +++ b/src/audio/midi.rs @@ -0,0 +1,101 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use once_cell::sync::Lazy; +use std::{io::Cursor, sync::Arc}; + +pub struct MidiSource { + // These are each 4410 long + left: Vec, + right: Vec, + sample_read_count: usize, + sequencer: rustysynth::MidiFileSequencer, +} + +impl MidiSource { + pub fn new(mut file: impl std::io::Read, looping: bool) -> Result { + let midi_file = Arc::new(rustysynth::MidiFile::new(&mut file).map_err(|e| e.to_string())?); + + let settings = rustysynth::SynthesizerSettings::new(44100); + let synthesizer = + rustysynth::Synthesizer::new(&SOUND_FONT, &settings).map_err(|e| e.to_string())?; + let mut sequencer = rustysynth::MidiFileSequencer::new(synthesizer); + + sequencer.play(&midi_file, looping); + + Ok(Self::new_sequencer(sequencer)) + } + + pub fn new_sequencer(sequencer: rustysynth::MidiFileSequencer) -> Self { + Self { + left: vec![0.; 4410], + right: vec![0.; 4410], + sample_read_count: 0, + sequencer, + } + } +} + +pub static SOUND_FONT: Lazy> = Lazy::new(|| { + let soundfont = include_bytes!("GMGSx.sf2"); + let mut cursor = Cursor::new(soundfont); + + rustysynth::SoundFont::new(&mut cursor) + .expect("failed to load sound font") + .into() +}); + +impl Iterator for MidiSource { + type Item = f32; + + fn next(&mut self) -> Option { + if self.sequencer.end_of_sequence() { + return None; + } + + let result = if self.sample_read_count % 2 == 0 { + self.left[self.sample_read_count / 2] + } else { + self.right[self.sample_read_count / 2] + }; + + self.sample_read_count += 1; + if self.sample_read_count >= 4410 * 2 { + self.sample_read_count = 0; + self.sequencer.render(&mut self.left, &mut self.right); + } + + Some(result) + } +} + +impl rodio::Source for MidiSource { + fn current_frame_len(&self) -> Option { + Some(4410 * 2 - self.sample_read_count) + } + + fn channels(&self) -> u16 { + 2 + } + + fn sample_rate(&self) -> u32 { + 44100 + } + + fn total_duration(&self) -> Option { + None + } +} diff --git a/src/audio.rs b/src/audio/mod.rs similarity index 61% rename from src/audio.rs rename to src/audio/mod.rs index a95aa8be..49da8f3d 100644 --- a/src/audio.rs +++ b/src/audio/mod.rs @@ -22,11 +22,9 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -use rodio::Decoder; -use rodio::{OutputStream, OutputStreamHandle, Sink}; - use crate::prelude::*; -use std::io::Cursor; + +mod midi; use strum::Display; use strum::EnumIter; @@ -50,17 +48,20 @@ pub struct Audio { struct Inner { // OutputStream is lazily evaluated specifically for wasm. web prevents autoplay without user interaction, this is a way of dealing with that. // To actually play tracks the user will have needed to interact with the ui. - _output_stream: OutputStream, - output_stream_handle: OutputStreamHandle, - sinks: HashMap, + _output_stream: rodio::OutputStream, + output_stream_handle: rodio::OutputStreamHandle, + sinks: HashMap, } +/// # Safety +/// cpal claims that Stream (which is why Inner is not send) is not thread safe on android, which is why it is not Send anywhere else. +/// We don't support android. The only other solution would be to use thread_local and... no. #[allow(unsafe_code)] unsafe impl Send for Inner {} impl Default for Audio { fn default() -> Self { - let (output_stream, output_stream_handle) = OutputStream::try_default().unwrap(); + let (output_stream, output_stream_handle) = rodio::OutputStream::try_default().unwrap(); Self { inner: Mutex::new(Inner { _output_stream: output_stream, @@ -73,22 +74,46 @@ impl Default for Audio { impl Audio { /// Play a sound on a source. - pub fn play(&self, path: String, volume: u8, pitch: u8, source: Source) -> Result<(), String> { + pub fn play( + &self, + path: impl AsRef, + volume: u8, + pitch: u8, + source: Source, + ) -> Result<(), String> { let mut inner = self.inner.lock(); // Create a sink - let sink = Sink::try_new(&inner.output_stream_handle).map_err(|e| e.to_string())?; + let sink = rodio::Sink::try_new(&inner.output_stream_handle).map_err(|e| e.to_string())?; + + let path = path.as_ref(); + let file = state!() + .filesystem + .open_file(path, filesystem::OpenFlags::Read) + .map_err(|e| e.to_string())?; - // Append the sound - let cursor = Cursor::new(state!().filesystem.read_bytes(path)?); // Select decoder type based on sound source match source { Source::SE | Source::ME => { // Non looping - sink.append(Decoder::new(cursor).map_err(|e| e.to_string())?); + if path + .extension() + .is_some_and(|e| matches!(e, "mid" | "midi")) + { + sink.append(midi::MidiSource::new(file, false)?); + } else { + sink.append(rodio::Decoder::new(file).map_err(|e| e.to_string())?); + } } _ => { // Looping - sink.append(Decoder::new_looped(cursor).map_err(|e| e.to_string())?); + if path + .extension() + .is_some_and(|e| matches!(e, "mid" | "midi")) + { + sink.append(midi::MidiSource::new(file, true)?); + } else { + sink.append(rodio::Decoder::new_looped(file).map_err(|e| e.to_string())?); + } } } @@ -100,6 +125,7 @@ impl Audio { // Add sink to hash, stop the current one if it's there. if let Some(s) = inner.sinks.insert(source, sink) { s.stop(); + s.sleep_until_end(); // wait for the sink to stop, there is a ~5ms delay where it will not }; Ok(()) @@ -121,6 +147,16 @@ impl Audio { } } + pub fn clear_sinks(&self) { + let mut inner = self.inner.lock(); + for (_, sink) in inner.sinks.iter_mut() { + sink.stop(); + // Sleeping ensures that the inner file is dropped. There is a delay of ~5ms where it is not dropped and this could lead to a panic + sink.sleep_until_end(); + } + inner.sinks.clear(); + } + /// Stop a source. pub fn stop(&self, source: &Source) { let mut inner = self.inner.lock(); diff --git a/src/cache/atlas.rs b/src/cache/atlas.rs new file mode 100644 index 00000000..fcd721fd --- /dev/null +++ b/src/cache/atlas.rs @@ -0,0 +1,44 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +#[derive(Default, Debug)] +pub struct Cache { + atlases: dashmap::DashMap, +} + +impl Cache { + pub fn load_atlas(&self, tileset: &rpg::Tileset) -> Result { + Ok(self + .atlases + .entry(tileset.id) + .or_try_insert_with(|| primitives::Atlas::new(tileset))? + .clone()) + } + + pub fn reload_atlas(&self, tileset: &rpg::Tileset) -> Result { + Ok(self + .atlases + .entry(tileset.id) + .insert(primitives::Atlas::new(tileset)?) + .clone()) + } + + pub fn clear(&self) { + self.atlases.clear() + } +} diff --git a/src/cache/data.rs b/src/cache/data.rs index 175eeec0..d7d59968 100644 --- a/src/cache/data.rs +++ b/src/cache/data.rs @@ -24,296 +24,263 @@ use crate::prelude::*; use core::ops::{Deref, DerefMut}; -/// A struct representing a cache of the current data. -/// This is done so data stored here can be written to the disk on demand. -#[derive(Default)] -pub struct Cache { - actors: AtomicRefCell>>, - animations: AtomicRefCell>>, - armors: AtomicRefCell>>, - classes: AtomicRefCell>>, - commonevents: AtomicRefCell>>, - enemies: AtomicRefCell>>, - items: AtomicRefCell>>, - mapinfos: AtomicRefCell>>, - scripts: AtomicRefCell>>, - skills: AtomicRefCell>>, - states: AtomicRefCell>>, - system: AtomicRefCell>, - tilesets: AtomicRefCell>>, - troops: AtomicRefCell>>, - weapons: AtomicRefCell>>, - - maps: dashmap::DashMap, - config: AtomicRefCell>, - commanddb: AtomicRefCell>, -} -macro_rules! save_data { - ($this:ident, $filesystem:ident, $($name:ident),*) => { - $( - paste::paste! { - let _bytes = $this - .[< $name:lower >] - .borrow() - .as_ref() - .map(|t| alox_48::to_bytes(t).map_err(|e| format!(concat!("Saving ", stringify!($name), ": {}"), e))); - - if let Some(_bytes) = _bytes { - $filesystem - .save_data(concat!("Data/", stringify!($name), ".rxdata"), _bytes?) - .map_err(|_| concat!("Failed to write", stringify!($name), "data"))?; - } - } - )* - }; -} - -macro_rules! load_data { - ($this:ident, $filesystem:ident, $($name:ident),*) => { - $( - paste::paste! { - *$this.[< $name:lower >].borrow_mut() = Some( - $filesystem - .read_data(concat!("Data/", stringify!($name), ".rxdata")) - .map_err(|s| format!(concat!("Failed to load ", stringify!($name) ,": {}"), s))?, - ); - } - )* - }; -} - -macro_rules! getter { - ($($name:ident, $type:ty),*) => { - $( - paste::paste! { - #[doc = "Get `" $name "` from the data cache. Panics if the project was not loaded."] - pub fn [< $name:lower>](&self) -> impl Deref + DerefMut + '_ { - AtomicRefMut::map(self.[< $name:lower >].borrow_mut(), |o| Option::as_mut(o).expect(concat!("Grabbing ", stringify!($name), " from the data cache failed because the project was not loaded. Please file this as an issue"))) - } - - #[doc = "Try getting `" $name "` from the data cache."] - pub fn [](&self) -> Option + DerefMut + '_ > { - AtomicRefMut::filter_map(self.[< $name:lower >].borrow_mut(), |o| Option::as_mut(o)) - } - - #[doc = "Get the raw optional `" $name "` from the data cache."] - pub fn [](&self) -> impl Deref> + DerefMut + '_ { - self.[< $name:lower >].borrow_mut() - } - } - )* - }; +#[derive(Default, Debug)] +pub struct Cache { + state: AtomicRefCell, } -macro_rules! setup_default { - ($this:ident, $($name:ident),*) => { - $( - paste::paste! { - *$this.[< $name:lower >].borrow_mut() = Some( - // This is a pretty dirty hack to make rustc assume that it's a vec of the type we're storing - NilPadded::from(vec![None, Some(Default::default())]) - ); - } - )* - }; +// Loaded is used 99% of the time. The size discrepancy is okay. +#[allow(clippy::large_enum_variant)] +#[derive(Default, Debug)] +enum State { + #[default] + Unloaded, + Loaded { + actors: AtomicRefCell, + animations: AtomicRefCell, + armors: AtomicRefCell, + classes: AtomicRefCell, + common_events: AtomicRefCell, + enemies: AtomicRefCell, + items: AtomicRefCell, + mapinfos: AtomicRefCell, + scripts: AtomicRefCell>, + skills: AtomicRefCell, + states: AtomicRefCell, + system: AtomicRefCell, + tilesets: AtomicRefCell, + troops: AtomicRefCell, + weapons: AtomicRefCell, + + maps: dashmap::DashMap, + }, } impl Cache { /// Load all data required when opening a project. + /// Does not load config. That is expected to have been loaded beforehand. pub fn load(&self) -> Result<(), String> { let filesystem = &state!().filesystem; - - if !filesystem.path_exists(".luminol") { - filesystem.create_directory(".luminol")?; - } - - let config = match filesystem - .read_bytes(".luminol/config") - .ok() - .and_then(|v| String::from_utf8(v).ok()) - .and_then(|s| ron::from_str(&s).ok()) - { - Some(c) => c, - None => { - let config = LocalConfig::default(); - filesystem - .save_data( - ".luminol/config", - ron::ser::to_string_pretty( - &config, - ron::ser::PrettyConfig::default().struct_names(true), - ) - .expect("Failed to serialize config"), - ) - .expect("Failed to write config data after failing to load config data"); - config - } - }; - - let commanddb = match filesystem - .read_bytes(".luminol/commands") - .ok() - .and_then(|v| String::from_utf8(v).ok()) - .and_then(|s| ron::from_str(&s).ok()) - { - Some(c) => c, - None => { - let config = CommandDB::new(config.editor_ver); - filesystem - .save_data( - ".luminol/commands", - ron::ser::to_string_pretty( - &config, - ron::ser::PrettyConfig::default().struct_names(true), - ) - .expect("Failed to serialize commands"), - ) - .expect("Failed to write config data after failing to load command data"); - config + let ext = self.rxdata_ext(); + + let mut scripts = None; + let scripts_paths = [ + project_config!().scripts_path.clone(), + "xScripts".to_string(), + "Scripts".to_string(), + ]; + for file in scripts_paths { + match filesystem.read_data(format!("Data/{file}.{ext}")) { + Ok(s) => { + scripts = Some(s); + project_config!().scripts_path = file; + break; + } + Err(e) => { + eprintln!("Error loading Data/{file}.{ext}: {e:#?}"); + } } - }; - - *self.config.borrow_mut() = Some(config); - *self.commanddb.borrow_mut() = Some(commanddb); - - load_data! { - self, filesystem, - Actors, Animations, Armors, - Classes, CommonEvents, Enemies, - Items, MapInfos, - Skills, States, System, - Tilesets, Troops, Weapons - } - - let mut scripts = filesystem.read_data("Data/xScripts.rxdata"); - - if let Err(e) = scripts { - eprintln!("Attempted loading xScripts failed with {e}"); - - scripts = filesystem.read_data("Data/Scripts.rxdata"); - } else { - self.config.borrow_mut().as_mut().unwrap().scripts_path = "xScripts".to_string(); } + let Some(scripts) = scripts else { + return Err("Unable to load scripts, tried scripts file from config, then xScripts, then Scripts".to_string()); + }; - *self.scripts.borrow_mut() = Some( - scripts.map_err(|s| format!("Failed to read Scripts (tried xScripts first): {s}"))?, + let actors = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Actors.{ext}")) + .map_err(|e| format!("Failed to load Actors.{ext}: {e}"))?, + ); + let animations = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Animations.{ext}")) + .map_err(|e| format!("Failed to load Animations.{ext}: {e}"))?, + ); + let armors = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Armors.{ext}")) + .map_err(|e| format!("Failed to load Armors.{ext}: {e}"))?, + ); + let classes = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Classes.{ext}")) + .map_err(|e| format!("Failed to load Classes.{ext}: {e}"))?, + ); + let common_events = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/CommonEvents.{ext}")) + .map_err(|e| format!("Failed to load CommonEvents.{ext}: {e}"))?, + ); + let enemies = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Enemies.{ext}")) + .map_err(|e| format!("Failed to load Enemies.{ext}: {e}"))?, + ); + let items = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Items.{ext}")) + .map_err(|e| format!("Failed to load Items.{ext}: {e}"))?, + ); + let mapinfos = AtomicRefCell::new( + filesystem + .read_data(format!("Data/MapInfos.{ext}")) + .map_err(|e| format!("Failed to load MapInfos.{ext}: {e}"))?, + ); + let skills = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Skills.{ext}")) + .map_err(|e| format!("Failed to load Skills.{ext}: {e}"))?, + ); + let states = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/States.{ext}")) + .map_err(|e| format!("Failed to load States.{ext}: {e}"))?, + ); + let system = AtomicRefCell::new( + filesystem + .read_data(format!("Data/System.{ext}")) + .map_err(|e| format!("Failed to load System.{ext}: {e}"))?, + ); + let tilesets = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Tilesets.{ext}")) + .map_err(|e| format!("Failed to load Tilesets.{ext}: {e}"))?, + ); + let troops = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Troops.{ext}")) + .map_err(|e| format!("Failed to load Troops.{ext}: {e}"))?, + ); + let weapons = AtomicRefCell::new( + filesystem + .read_nil_padded(format!("Data/Weapons.{ext}")) + .map_err(|e| format!("Failed to load Weapons.{ext}: {e}"))?, ); - self.maps.clear(); - Ok(()) - } - - /// Load a map. - pub fn load_map( - &self, - id: i32, - ) -> Result + DerefMut + '_, String> { - self.maps.entry(id).or_try_insert_with(|| { - state!() - .filesystem - .read_data(format!("Data/Map{id:0>3}.rxdata",)) - .map_err(|e| format!("Failed to load map: {e}")) - }) - } - - /// Get a map that has been loaded. This function is not async unlike [`Self::load_map`]. - /// # Panics - /// Will panic if the map has not been loaded already. - pub fn get_map(&self, id: i32) -> impl Deref + DerefMut + '_ { - self.maps.get_mut(&id).expect("map not loaded") - } + *self.state.borrow_mut() = State::Loaded { + actors, + animations, + armors, + classes, + common_events, + enemies, + items, + mapinfos, + skills, + states, + system, + tilesets, + troops, + weapons, + + maps: Default::default(), + scripts: AtomicRefCell::new(scripts), + }; - getter! { - Actors, NilPadded, - Animations, NilPadded, - Armors, NilPadded, - Classes, NilPadded, - CommonEvents, NilPadded, - Enemies, NilPadded, - Items, NilPadded, - MapInfos, HashMap, - Scripts, Vec, - Skills, NilPadded, - States, NilPadded, - System, rpg::System, - Tilesets, NilPadded, - Troops, NilPadded, - Weapons, NilPadded, - - Config, LocalConfig, - CommandDB, CommandDB + Ok(()) } - /// Save the local config. - pub fn save_config(&self, filesystem: &Filesystem) -> Result<(), String> { - if !filesystem.path_exists(".luminol") { - filesystem.create_directory(".luminol")?; + pub fn rxdata_ext(&self) -> &'static str { + match project_config!().editor_ver { + config::RMVer::XP => "rxdata", + config::RMVer::VX => "rvdata", + config::RMVer::Ace => "rvdata2", } + } - let config_str = ron::ser::to_string_pretty( - &*self.config(), - ron::ser::PrettyConfig::default().struct_names(true), - ) - .map_err(|e| format!("Failed to serialize config data: {e}"))?; + /// Save all cached data to disk. + /// Will flush the cache too. + pub fn save(&self) -> Result<(), String> { + config::Project::save()?; - filesystem - .save_data(".luminol/config", config_str) - .map_err(|_| "Failed to write Config data")?; + let filesystem = &state!().filesystem; - let commands_str = ron::ser::to_string_pretty( - &*self.commanddb(), - ron::ser::PrettyConfig::default().struct_names(true), - ) - .map_err(|e| format!("Failed to serialize command data: {e}"))?; + let state = self.state.borrow(); + let State::Loaded { + actors, + animations, + armors, + classes, + common_events, + enemies, + items, + mapinfos, + scripts, + skills, + states, + system, + tilesets, + troops, + weapons, + maps, + } = &*state + else { + return Err("Project not loaded".to_string()); + }; - filesystem - .save_data(".luminol/commands", commands_str) - .map_err(|_| "Failed to write Config data")?; + let ext = self.rxdata_ext(); + + filesystem.save_nil_padded(format!("Data/Actors.{ext}"), &actors.borrow())?; + filesystem.save_nil_padded(format!("Data/Animations.{ext}"), &animations.borrow())?; + filesystem.save_nil_padded(format!("Data/Armors.{ext}"), &armors.borrow())?; + filesystem.save_nil_padded(format!("Data/Classes.{ext}"), &classes.borrow())?; + filesystem.save_nil_padded(format!("Data/CommonEvents.{ext}"), &common_events.borrow())?; + filesystem.save_nil_padded(format!("Data/Enemies.{ext}"), &enemies.borrow())?; + filesystem.save_nil_padded(format!("Data/Items.{ext}"), &items.borrow())?; + filesystem.save_data(format!("Data/MapInfos.{ext}"), &*mapinfos.borrow())?; + filesystem.save_nil_padded(format!("Data/Scripts.{ext}"), &scripts.borrow())?; + filesystem.save_nil_padded(format!("Data/Skills.{ext}"), &skills.borrow())?; + filesystem.save_nil_padded(format!("Data/States.{ext}"), &states.borrow())?; + filesystem.save_data(format!("Data/System.{ext}"), &*system.borrow())?; + filesystem.save_nil_padded(format!("Data/Tilesets.{ext}"), &tilesets.borrow())?; + filesystem.save_nil_padded(format!("Data/Troops.{ext}"), &troops.borrow())?; + filesystem.save_nil_padded(format!("Data/Weapons.{ext}"), &weapons.borrow())?; + + for entry in maps.iter() { + filesystem.save_data(format!("Data/Map{:0>3}.{ext}", entry.key()), entry.value())? + } + maps.clear(); Ok(()) } - /// Save all cached data to disk. - /// Will flush the cache too. - pub fn save(&self, filesystem: &Filesystem) -> Result<(), String> { - self.system().magic_number = rand::random(); - - // Write map data and clear map cache. - // We serialize all of these first before writing them to the disk to avoid bringing a AtomicRefCell across an await. - // A RwLock may be used in the future to solve this, though. - for entry in self.maps.iter() { - let bytes = alox_48::to_bytes(entry.value()).map_err(|e| e.to_string())?; - filesystem - .save_data(format!("Data/Map{:0>3}.rxdata", entry.key()), bytes) - .map_err(|e| format!("Failed to write Map data {e}"))?; + pub async fn create_project(&self, config: config::project::Config) -> Result<(), String> { + if let Some(path) = rfd::AsyncFileDialog::default().pick_folder().await { + let path = path.path().join(&config.project_name); + std::fs::create_dir(path).map_err(|e| e.to_string())?; + + let command_db = config::CommandDB::new(config.editor_ver); + + let mut game_ini = ini::Ini::new(); + game_ini + .with_section(Some("Game")) + .set("Library", "RGSS104E.dll") + .set("Scripts", &config.scripts_path) + .set("Title", &config.project_name) + .set("RTP1", "") + .set("RTP2", "") + .set("RTP3", ""); + + *config::PROJECT.borrow_mut() = config::Project::Loaded { + command_db, + config, + game_ini, + }; + + self.setup_defaults(); + self.save()?; + } else { + return Err("Cancelled picking a project directory".to_string()); } - let scripts_bytes = - alox_48::to_bytes(&*self.scripts()).map_err(|e| format!("Saving Scripts: {e}"))?; - filesystem - .save_data( - format!("Data/{}.rxdata", self.config().scripts_path), - scripts_bytes, - ) - .map_err(|e| format!("Failed to write Script data {e}"))?; - - save_data! { - self, filesystem, - Actors, Animations, Armors, - Classes, CommonEvents, Enemies, - Items, MapInfos, - Skills, States, System, // FIXME: save to xScripts too! - Tilesets, Troops, Weapons - }; - - self.save_config(filesystem) + Ok(()) } /// Setup default values + // FIXME: Code jank pub fn setup_defaults(&self) { - let mut map_infos = HashMap::new(); - map_infos.insert( + let mut mapinfos = rpg::MapInfos::new(); + mapinfos.insert( 1, rpg::MapInfo { parent_id: 0, @@ -324,19 +291,10 @@ impl Cache { scroll_y: 0, }, ); - *self.mapinfos.borrow_mut() = Some(map_infos); - - // FIXME: make this static somehow? - *self.scripts.borrow_mut() = - Some(alox_48::from_bytes(include_bytes!("Scripts.rxdata")).unwrap()); - - *self.system.borrow_mut() = Some(rpg::System { - magic_number: rand::random(), - ..Default::default() - }); + let mapinfos = mapinfos.into(); - self.maps.clear(); - self.maps.insert( + let maps = dashmap::DashMap::new(); + maps.insert( 1, rpg::Map { tileset_id: 1, @@ -347,15 +305,151 @@ impl Cache { }, ); - *self.config.borrow_mut() = Some(LocalConfig::default()); - *self.commanddb.borrow_mut() = Some(CommandDB::default()); + let system = rpg::System { + magic_number: rand::random(), + ..Default::default() + } + .into(); + + let scripts = match project_config!().editor_ver { + config::RMVer::XP => alox_48::from_bytes(include_bytes!("Scripts.rxdata")).unwrap(), + config::RMVer::VX => todo!(), + config::RMVer::Ace => todo!(), + }; + let scripts = AtomicRefCell::new(scripts); + + *self.state.borrow_mut() = State::Loaded { + actors: AtomicRefCell::new(vec![rpg::Actor::default()]), + animations: AtomicRefCell::new(vec![rpg::Animation::default()]), + armors: AtomicRefCell::new(vec![rpg::Armor::default()]), + classes: AtomicRefCell::new(vec![rpg::Class::default()]), + common_events: AtomicRefCell::new(vec![rpg::CommonEvent::default()]), + enemies: AtomicRefCell::new(vec![rpg::Enemy::default()]), + items: AtomicRefCell::new(vec![rpg::Item::default()]), + skills: AtomicRefCell::new(vec![rpg::Skill::default()]), + states: AtomicRefCell::new(vec![rpg::State::default()]), + tilesets: AtomicRefCell::new(vec![rpg::Tileset::default()]), + troops: AtomicRefCell::new(vec![rpg::Troop::default()]), + weapons: AtomicRefCell::new(vec![rpg::Weapon::default()]), + + mapinfos, + maps, + system, + scripts, + }; + } +} + +macro_rules! nested_ref_getter { + ($( + $typ:ty, $name:ident, $($enum_type:ident :: $variant:ident),+ + );*) => { + $( + #[allow(unsafe_code, dead_code)] + pub fn $name<'a>(&'a self) -> impl core::ops::DerefMut + 'a { + struct _Ref<'b> { + _this_ref: atomic_refcell::AtomicRef<'b, State>, + _other_ref: atomic_refcell::AtomicRefMut<'b, $typ>, + } + impl<'b> core::ops::Deref for _Ref<'b> { + type Target = $typ; + + fn deref(&self) -> &Self::Target { + &self._other_ref + } + } + impl<'b> core::ops::DerefMut for _Ref<'b> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self._other_ref + } + } + + let _this_ref = self.state.borrow(); + let _other_ref: atomic_refcell::AtomicRefMut<'a, $typ> = unsafe { + // See Self::map for safety + match &*(&*_this_ref as *const _) { + $( + $enum_type::$variant { $name, .. } => $name.borrow_mut(), + )+ + _ => panic!("Project not loaded"), + } + }; + + _Ref { + _this_ref, + _other_ref, + } + } + )+ + }; + +} + +impl Cache { + nested_ref_getter! { + rpg::Actors, actors, State::Loaded; + rpg::Animations, animations, State::Loaded; + rpg::Armors, armors, State::Loaded; + rpg::Classes, classes, State::Loaded; + rpg::CommonEvents, common_events, State::Loaded; + rpg::Enemies, enemies, State::Loaded; + rpg::Items, items, State::Loaded; + rpg::MapInfos, mapinfos, State::Loaded; + Vec, scripts, State::Loaded; + rpg::Skills, skills, State::Loaded; + rpg::States, states, State::Loaded; + rpg::System, system, State::Loaded; + rpg::Tilesets, tilesets, State::Loaded; + rpg::Troops, troops, State::Loaded; + rpg::Weapons, weapons, State::Loaded + } + + /// Load a map. + #[allow(unsafe_code)] + #[allow(clippy::panic)] + pub fn map<'a>(&'a self, id: usize) -> impl DerefMut + 'a { + struct Ref<'b> { + _state: atomic_refcell::AtomicRef<'b, State>, + map_ref: dashmap::mapref::one::RefMut<'b, usize, rpg::Map>, + } + impl<'b> Deref for Ref<'b> { + type Target = rpg::Map; + + fn deref(&self) -> &Self::Target { + &self.map_ref + } + } + impl<'b> DerefMut for Ref<'b> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map_ref + } + } + + let state = self.state.borrow(); + let State::Loaded { ref maps, .. } = &*state else { + panic!("project not loaded") + }; + //? # SAFETY + // For starters, this has been tested against miri. Miri is okay with it. + // Ref is self referential- map_ref borrows from _state. We need to store _state so it gets dropped at the same time as map_ref. + // If it didn't, map_ref would invalidate the refcell. We could unload the project, changing State while map_ref is live. Storing _state prevents this. + // Because the rust borrow checker isn't smart enough for this, we need to create an unbounded reference to maps to get a map out. We're not actually using this reference + // for any longer than it would be valid for (notice the fact that we assign map_ref a lifetime of 'a, which is the lifetime it should have anyway) so this is okay. + let map_ref: dashmap::mapref::one::RefMut<'a, _, _> = unsafe { + let unsafe_maps_ref: &dashmap::DashMap = &*(maps as *const _); + unsafe_maps_ref + .entry(id) + .or_try_insert_with(|| { + state!() + .filesystem + .read_data(format!("Data/Map{id:0>3}.{}", self.rxdata_ext())) + }) + .expect("failed to load map") // FIXME + }; - setup_default! { - self, - Actors, Animations, Armors, - Classes, CommonEvents, Enemies, - Items, Skills, States, - Tilesets, Troops, Weapons + Ref { + _state: state, + map_ref, } } } diff --git a/src/cache/image_cache.rs b/src/cache/image_cache.rs index fc31ce25..ad30612b 100644 --- a/src/cache/image_cache.rs +++ b/src/cache/image_cache.rs @@ -22,51 +22,50 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use eframe::wgpu::util::DeviceExt; +use once_cell::sync::Lazy; + use crate::prelude::*; -use glow::HasContext; #[derive(Default)] pub struct Cache { // FIXME: This may not handle reloading textures properly. - egui_imgs: dashmap::DashMap>, - glow_imgs: dashmap::DashMap>, + retained_images: dashmap::DashMap>, + wgpu_textures: dashmap::DashMap>, } -pub struct GlTexture { - raw: glow::Texture, - width: u32, - height: u32, +#[derive(Debug)] +pub struct WgpuTexture { + pub texture: wgpu::Texture, + pub bind_group: wgpu::BindGroup, } -impl GlTexture { - /// # Safety - /// Do not free the returned texture using glow::Context::delete_texture. - #[allow(unsafe_code)] - pub unsafe fn raw(&self) -> glow::Texture { - self.raw +impl WgpuTexture { + pub fn new(texture: wgpu::Texture, bind_group: wgpu::BindGroup) -> Self { + Self { + texture, + bind_group, + } } - pub fn width(&self) -> u32 { - self.width + pub fn size(&self) -> wgpu::Extent3d { + self.texture.size() } - pub fn height(&self) -> u32 { - self.height + pub fn size_vec2(&self) -> egui::Vec2 { + egui::vec2(self.texture.width() as _, self.texture.height() as _) } - pub fn size_vec2(&self) -> egui::Vec2 { - egui::vec2(self.width as _, self.height as _) + pub fn width(&self) -> u32 { + self.texture.width() } -} -impl Drop for GlTexture { - fn drop(&mut self) { - // Delete the texture on drop. - // This assumes that the texture is valid. - #[allow(unsafe_code)] - unsafe { - state!().gl.delete_texture(self.raw) - } + pub fn height(&self) -> u32 { + self.texture.height() + } + + pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { + render_pass.set_bind_group(0, &self.bind_group, &[]); } } @@ -80,85 +79,164 @@ impl Cache { let filename = filename.as_ref(); let entry = self - .egui_imgs + .retained_images .entry(format!("{directory}/{filename}")) - .or_try_insert_with(|| { - let Some(f) = state!().filesystem.dir_children(directory)?.map(Result::unwrap).find(|entry| { - entry.path().file_stem() == Some(std::ffi::OsStr::new(filename)) - }) else { - return Err("image not found".to_string()); - }; - - egui_extras::RetainedImage::from_image_bytes( + .or_try_insert_with(|| -> Result<_, String> { + let image = self.load_image(directory, filename)?.into_rgba8(); + let image = egui_extras::RetainedImage::from_color_image( format!("{directory}/{filename}"), - std::fs::read(f.path()) - .map_err(|e| e.to_string())? - .as_slice(), + egui::ColorImage::from_rgba_unmultiplied( + [image.width() as usize, image.height() as usize], + &image, + ), ) - .map(|i| i.with_options(egui::TextureOptions::NEAREST)) - .map(Arc::new) + .with_options(egui::TextureOptions::NEAREST); + Ok(Arc::new(image)) })?; Ok(Arc::clone(&entry)) } - /// # Safety - /// Do not free the returned texture using glow::Context::delete_texture. - /// All other safety rules when working with OpenGl apply. - #[allow(unsafe_code)] - pub unsafe fn load_glow_image( + pub fn load_image( + &self, + directory: impl AsRef, + filename: impl AsRef, + ) -> Result { + let path = directory.as_ref().join(filename); + let image = + image::load_from_memory(&state!().filesystem.read(path).map_err(|e| e.to_string())?) + .map_err(|e| e.to_string())?; + Ok(image) + } + + pub fn create_texture_bind_group(texture: &wgpu::Texture) -> wgpu::BindGroup { + let render_state = &state!().render_state; + // We *really* don't care about the fields here. + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + // We want our texture to use Nearest filtering and repeat. + // The only time our texture should be repeating is for fogs and panoramas. + let sampler = render_state + .device + .create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::Repeat, + address_mode_v: wgpu::AddressMode::Repeat, + address_mode_w: wgpu::AddressMode::Repeat, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + // Create the bind group + // Again, I have no idea why its setup this way + render_state + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: Self::bind_group_layout(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }) + } + + pub fn load_wgpu_image( &self, directory: impl AsRef, filename: impl AsRef, - ) -> Result, String> { + ) -> Result, String> { let directory = directory.as_ref(); let filename = filename.as_ref(); let entry = self - .glow_imgs + .wgpu_textures .entry(format!("{directory}/{filename}")) - .or_try_insert_with(|| { - let Some(f) = state!().filesystem.dir_children(directory)?.map(Result::unwrap).find(|entry| { - entry.path().file_stem() == Some(std::ffi::OsStr::new(filename)) - }) else { - return Err("image not found".to_string()); - }; - - let image = image::open(f.path()) - .map_err(|e| e.to_string())?; + .or_try_insert_with(|| -> Result<_, String> { // We force the image to be rgba8 to avoid any weird texture errors. - // If the image was not rgba8 (say it was rgb8) we would also get a segfault as opengl is expecting a series of bytes with the len of width * height * 4. - let image = image.to_rgba8(); + // If the image was not rgba8 (say it was rgb8) we would get weird texture errors + let image = self.load_image(directory, filename)?.into_rgba8(); // Check that the image will fit into the texture // If we dont perform this check, we may get a segfault (dont ask me how i know this) assert_eq!(image.len() as u32, image.width() * image.height() * 4); - - let raw = state!().gl.create_texture()?; - state!().gl.bind_texture(glow::TEXTURE_2D, Some(raw)); - - state!().gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::RGBA as _, - image.width() as _, - image.height() as _, - 0, - glow::RGBA, - glow::UNSIGNED_BYTE, - Some(image.as_raw()), + let render_state = &state!().render_state; + // Create the texture and upload the data at the same time. + // This is just a utility function to avoid boilerplate + let texture = render_state.device.create_texture_with_data( + &render_state.queue, + &wgpu::TextureDescriptor { + label: Some(&format!("{directory}/{filename}")), + size: wgpu::Extent3d { + width: image.width(), + height: image.height(), + depth_or_array_layers: 1, + }, + dimension: wgpu::TextureDimension::D2, + mip_level_count: 1, + sample_count: 1, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + &image, ); - state!().gl.generate_mipmap(glow::TEXTURE_2D); + let bind_group = Self::create_texture_bind_group(&texture); + + let texture = WgpuTexture { + texture, + bind_group, + }; - Ok(Arc::new(GlTexture { - raw, - width: image.width(), - height: image.height(), - })) + Ok(Arc::new(texture)) })?; Ok(Arc::clone(&entry)) } pub fn clear(&self) { - self.egui_imgs.clear(); - self.glow_imgs.clear(); + self.retained_images.clear(); + self.wgpu_textures.clear(); + } + + pub fn bind_group_layout() -> &'static wgpu::BindGroupLayout { + &LAYOUT } } + +static LAYOUT: Lazy = Lazy::new(|| { + let render_state = &state!().render_state; + + render_state + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + // I just copy pasted this stuff from the wgpu guide. + // No clue why I need it. + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + // This should match the filterable field of the + // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }) +}); diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 7e0698c0..358ae9ff 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -22,5 +22,6 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +pub mod atlas; pub mod data; pub mod image_cache; diff --git a/src/command_gen/mod.rs b/src/command_gen/mod.rs index 1866cb79..1e30f480 100644 --- a/src/command_gen/mod.rs +++ b/src/command_gen/mod.rs @@ -31,7 +31,7 @@ pub struct CommandGeneratorWindow { impl Default for CommandGeneratorWindow { fn default() -> Self { Self { - commands: state!().data_cache.commanddb().user.clone(), + commands: command_db!().user.clone(), ui_examples: vec![], } } @@ -217,7 +217,7 @@ impl window::Window for CommandGeneratorWindow { } if ui.button("Save").clicked() { - state!().data_cache.commanddb().user = self.commands.clone(); + command_db!().user = self.commands.clone(); } }); }); diff --git a/src/components/command_view/command_ui.rs b/src/components/command_view/command_ui.rs index b5d823f6..fc4811a4 100644 --- a/src/components/command_view/command_ui.rs +++ b/src/components/command_view/command_ui.rs @@ -31,7 +31,7 @@ impl CommandView { pub fn command_ui<'i, I>( &mut self, ui: &mut egui::Ui, - db: &CommandDB, + db: &config::CommandDB, (index, command): (usize, &'i mut rpg::EventCommand), iter: &mut std::iter::Peekable, ) where @@ -153,7 +153,7 @@ impl CommandView { Color32::YELLOW )); if highlight { - let theme = state!().saved_state.borrow().theme; + let theme = global_config!().theme; ui.selectable_value( &mut self.selected_index, @@ -218,7 +218,7 @@ fn parameter_label( *i as i32 == match command.parameters.get_mut(index.as_usize()) { Some(i) => *i.into_integer(), - None => return false + None => return false, } }) else { return write!(string, " invalid selection"); diff --git a/src/components/command_view/ui.rs b/src/components/command_view/ui.rs index 8afc89a8..55196221 100644 --- a/src/components/command_view/ui.rs +++ b/src/components/command_view/ui.rs @@ -30,7 +30,12 @@ use itertools::Itertools; impl CommandView { #[allow(clippy::ptr_arg)] - pub fn ui(&mut self, ui: &mut egui::Ui, db: &CommandDB, commands: &mut Vec) { + pub fn ui( + &mut self, + ui: &mut egui::Ui, + db: &config::CommandDB, + commands: &mut Vec, + ) { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); let mut iter = commands.iter_mut().enumerate().peekable(); while let Some(i) = iter.next() { @@ -102,7 +107,7 @@ impl CommandView { .desired_width(f32::INFINITY); let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { - let theme = state!().saved_state.borrow().theme; + let theme = global_config!().theme; let mut layout_job = syntax_highlighting::highlight( ui.ctx(), theme, diff --git a/src/components/map_view.rs b/src/components/map_view.rs new file mode 100644 index 00000000..634c891d --- /dev/null +++ b/src/components/map_view.rs @@ -0,0 +1,488 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::prelude::*; + +use super::tilepicker; + +#[derive(Debug)] +pub struct MapView { + /// Toggle to display the visible region in-game. + pub visible_display: bool, + /// Toggle move route preview + pub move_preview: bool, + + pub pan: egui::Vec2, + pub inter_tile_pan: egui::Vec2, + + pub events: rmxp_types::OptionVec, + pub map: Map, + + pub selected_layer: SelectedLayer, + pub selected_event_id: Option, + pub cursor_pos: egui::Pos2, + pub event_enabled: bool, + pub snap_to_grid: bool, + + /// The map coordinates of the tile being hovered over + pub hover_tile: Option, + + /// True if selected_event_id is being hovered over by the mouse + /// (as opposed to the map cursor) + /// and false otherwise + pub selected_event_is_hovered: bool, + + pub darken_unselected_layers: bool, + + pub scale: f32, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] +pub enum SelectedLayer { + #[default] + Events, + Tiles(usize), +} + +impl MapView { + pub fn new(map: &rpg::Map, tileset: &rpg::Tileset) -> Result { + // Get tilesets. + + let atlas = state!().atlas_cache.load_atlas(tileset)?; + let events = map + .events + .iter() + .map(|(id, e)| Event::new(e, &atlas).map(|o| o.map(|e| (id, e)))) + .flatten_ok() + .try_collect()?; + let map = Map::new(map, tileset)?; + + Ok(Self { + visible_display: false, + move_preview: false, + + pan: egui::Vec2::ZERO, + inter_tile_pan: egui::Vec2::ZERO, + + events, + map, + + selected_layer: SelectedLayer::default(), + selected_event_id: None, + cursor_pos: egui::Pos2::ZERO, + event_enabled: true, + snap_to_grid: false, + + darken_unselected_layers: true, + + hover_tile: None, + + selected_event_is_hovered: false, + + scale: 100., + }) + } + + pub fn ui( + &mut self, + ui: &mut egui::Ui, + map: &rpg::Map, + tilepicker: &Tilepicker, + dragging_event: bool, + drawing_shape: bool, + drawing_shape_pos: Option, + force_show_pattern_rect: bool, + ) -> egui::Response { + // Allocate the largest size we can for the tilemap + let canvas_rect = ui.max_rect(); + let canvas_center = canvas_rect.center(); + ui.set_clip_rect(canvas_rect); + + let mut response = ui.allocate_rect(canvas_rect, egui::Sense::click_and_drag()); + + // Handle zoom + if let Some(pos) = response.hover_pos() { + // We need to store the old scale before applying any transformations + let old_scale = self.scale; + let delta = ui.input(|i| i.scroll_delta.y * 5.); + + // Apply scroll and cap max zoom to 15% + self.scale += delta / 30.; + self.scale = self.scale.max(15.).min(300.); + + // Get the normalized cursor position relative to pan + let pos_norm = (pos - self.pan - canvas_center) / old_scale; + // Offset the pan to the cursor remains in the same place + // Still not sure how the math works out, if it ain't broke don't fix it + self.pan = pos - canvas_center - pos_norm * self.scale + self.inter_tile_pan; + } + + let ctrl_drag = ui.input(|i| { + // Handle pan + if i.key_pressed(egui::Key::ArrowUp) && self.cursor_pos.y > 0. { + self.cursor_pos.y -= 1.0; + } + if i.key_pressed(egui::Key::ArrowDown) + && self.cursor_pos.y < map.data.ysize() as f32 - 1. + { + self.cursor_pos.y += 1.0; + } + if i.key_pressed(egui::Key::ArrowLeft) && self.cursor_pos.x > 0. { + self.cursor_pos.x -= 1.0; + } + if i.key_pressed(egui::Key::ArrowRight) + && self.cursor_pos.x < map.data.xsize() as f32 - 1. + { + self.cursor_pos.x += 1.0; + } + + i.modifiers.command && response.dragged_by(egui::PointerButton::Primary) + }); + + let panning_map_view = response.dragged_by(egui::PointerButton::Middle) || ctrl_drag; + + if panning_map_view { + self.pan += response.drag_delta(); + ui.ctx().request_repaint(); + } + + // Handle cursor icon + if panning_map_view { + response = response.on_hover_cursor(egui::CursorIcon::Grabbing); + } else { + response = response.on_hover_cursor(egui::CursorIcon::Grab); + } + + // Determine some values which are relatively constant + // If we don't use pixels_per_point then the map is the wrong size. + // *don't ask me how i know this*. + // its a *long* story + let scale = self.scale / (ui.ctx().pixels_per_point() * 100.); + let tile_size = 32. * scale; + + if self.snap_to_grid { + self.inter_tile_pan = egui::vec2(self.pan.x % tile_size, self.pan.y % tile_size); + self.pan -= self.inter_tile_pan; + } + + let canvas_pos = canvas_center + self.pan; + + // We check here after we calculate the scale and whatnot + self.hover_tile = None; + if let Some(pos) = response.hover_pos() { + let mut pos_tile = (pos - self.pan - canvas_center) / tile_size + + egui::Vec2::new(map.width as f32 / 2., map.height as f32 / 2.); + // Force the cursor to a tile instead of in-between + pos_tile.x = pos_tile.x.floor().clamp(0., map.width as f32 - 1.); + pos_tile.y = pos_tile.y.floor().clamp(0., map.height as f32 - 1.); + self.hover_tile = Some(pos_tile.to_pos2()); + // Handle input + if matches!(self.selected_layer, SelectedLayer::Tiles(_)) + || dragging_event + || response.clicked() + { + self.cursor_pos = pos_tile.to_pos2(); + } + } + + let width2 = map.width as f32 / 2.; + let height2 = map.height as f32 / 2.; + + let pos = egui::Vec2::new(width2 * tile_size, height2 * tile_size); + let map_rect = egui::Rect { + min: canvas_pos - pos, + max: canvas_pos + pos, + }; + + self.map.paint( + ui.painter(), + match self.selected_layer { + SelectedLayer::Events => None, + SelectedLayer::Tiles(selected_layer) if self.darken_unselected_layers => { + Some(selected_layer) + } + SelectedLayer::Tiles(_) => None, + }, + map_rect, + ); + + ui.painter().rect_stroke( + map_rect, + 5., + egui::Stroke::new(3., egui::Color32::DARK_GRAY), + ); + + let cursor_rect = egui::Rect::from_min_size( + map_rect.min + (self.cursor_pos.to_vec2() * tile_size), + egui::Vec2::splat(tile_size), + ); + let pattern_rect = egui::Rect::from_min_size( + map_rect.min + (self.cursor_pos.to_vec2() * tile_size), + if !force_show_pattern_rect && drawing_shape_pos.is_some() { + egui::Vec2::splat(tile_size) + } else { + egui::vec2( + tile_size + * (tilepicker.selected_tiles_right - tilepicker.selected_tiles_left + 1) + as f32, + tile_size + * (tilepicker.selected_tiles_bottom - tilepicker.selected_tiles_top + 1) + as f32, + ) + }, + ) + .intersect(map_rect); + + if !self.event_enabled || !matches!(self.selected_layer, SelectedLayer::Events) { + self.selected_event_id = None; + } + self.selected_event_is_hovered = false; + + if self.event_enabled { + let mut selected_event = None; + let mut selected_event_rects = None; + + for (_, event) in map.events.iter() { + let sprite = self.events.get(event.id); + let event_size = sprite + .map(|e| e.sprite_size) + .unwrap_or(egui::vec2(32., 32.)); + let scaled_event_size = event_size * scale; + + // Darken the graphic if required + if let Some(sprite) = sprite { + sprite.sprite().graphic.set_opacity_multiplier( + if self.darken_unselected_layers + && !matches!(self.selected_layer, SelectedLayer::Events) + { + 0.5 + } else { + 1. + }, + ); + } + + let tile_rect = egui::Rect::from_min_size( + map_rect.min + + egui::vec2(event.x as f32 * tile_size, event.y as f32 * tile_size), + egui::vec2(32., 32.) * scale, + ); + let box_rect = egui::Rect::from_min_size( + map_rect.min + + egui::vec2( + (event.x as f32 * tile_size) + (tile_size - scaled_event_size.x) / 2., + (event.y as f32 * tile_size) + (tile_size - scaled_event_size.y), + ), + scaled_event_size, + ); + + if let Some(sprite) = sprite { + sprite.paint(ui.painter(), box_rect); + } + + if matches!(self.selected_layer, SelectedLayer::Events) + && ui.input(|i| !i.modifiers.shift) + { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ); + + // If the mouse is not hovering over an event, then we will handle the selected + // tile based on where the map cursor is + if !self.selected_event_is_hovered && !dragging_event { + selected_event = match selected_event { + // If the map cursor is on the exact tile of an event, then that is the + // selected event + Some(_) + if self.cursor_pos.x == event.x as f32 + && self.cursor_pos.y == event.y as f32 => + { + Some(event) + } + Some(e) + if self.cursor_pos.x == e.x as f32 + && self.cursor_pos.y == e.y as f32 => + { + selected_event + } + // Otherwise if the map cursor intersects at least one event's graphic, + // then the one out of those with the highest ID should be the selected + // event + _ if box_rect.contains(cursor_rect.center()) => Some(event), + _ => selected_event, + }; + if let Some(e) = selected_event { + if e.id == event.id { + selected_event_rects = Some((tile_rect, box_rect)); + } + } + } + + if ui.rect_contains_pointer(box_rect) { + response = response.on_hover_ui_at_pointer(|ui| { + ui.label(format!("Event {:0>3}: {:?}", event.id, event.name)); + + let (response, painter) = ui.allocate_painter( + event_size * ui.ctx().pixels_per_point(), + egui::Sense::click(), + ); + if let Some(sprite) = sprite { + sprite.paint(&painter, response.rect); + } + match self.selected_event_id { + Some(id) if id == event.id => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(2., egui::Color32::YELLOW), + ), + _ => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ), + } + }); + + if let Some(hover_tile) = self.hover_tile { + if !dragging_event { + // Handle which event should be considered selected based on the + // hovered tile + selected_event = match selected_event { + // If the cursor is hovering over the exact tile of an event, then that is + // the selected event + Some(_) + if hover_tile.x == event.x as f32 + && hover_tile.y == event.y as f32 => + { + Some(event) + } + Some(e) + if hover_tile.x == e.x as f32 + && hover_tile.y == e.y as f32 => + { + selected_event + } + // Otherwise if the cursor is hovering over at least one event's graphic, + // then the one out of those with the highest ID should be the selected event + _ => Some(event), + }; + if let Some(e) = selected_event { + if e.id == event.id { + self.selected_event_is_hovered = true; + selected_event_rects = Some((tile_rect, box_rect)); + } + } + } + } + } + + // If an event is being dragged, that should always be the selected event + if let Some(id) = self.selected_event_id { + if dragging_event && id == event.id { + selected_event = Some(event); + selected_event_rects = Some((tile_rect, box_rect)); + } + } + } else { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(1., egui::Color32::DARK_GRAY), + ); + } + + // Draw a magenta rectangle on the border of events that are being edited + if event.extra_data.is_editor_open { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(3., egui::Color32::from_rgb(255, 0, 255)), + ); + } + } + + self.selected_event_id = selected_event.map(|e| e.id); + + // Draw a yellow rectangle on the border of the selected event's graphic + if let Some(selected_event) = selected_event { + // Make sure the event editor isn't open so we don't draw over the + // magenta rectangle + if !selected_event.extra_data.is_editor_open { + if let Some((tile_rect, box_rect)) = selected_event_rects { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(3., egui::Color32::YELLOW), + ); + } + } + } + } + + // Do we display the visible region? + if self.visible_display { + // Determine the visible region. + let width2: f32 = (640. / 2.) * scale; + let height2: f32 = (480. / 2.) * scale; + + let pos = egui::Vec2::new(width2, height2); + let visible_rect = egui::Rect { + min: canvas_center - pos, + max: canvas_center + pos, + }; + + // Show the region. + ui.painter().rect_stroke( + visible_rect, + 5., + egui::Stroke::new(1., egui::Color32::YELLOW), + ); + } + + // Draw the origin tile for the rectangle and circle brushes + if drawing_shape { + if let Some(drawing_shape_pos) = drawing_shape_pos { + let drawing_shape_rect = egui::Rect::from_min_size( + map_rect.min + (drawing_shape_pos.to_vec2() * tile_size), + egui::Vec2::splat(tile_size), + ); + ui.painter().rect_stroke( + drawing_shape_rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ); + } + } + + // Display cursor. + if matches!(self.selected_layer, SelectedLayer::Tiles(_)) { + ui.painter().rect_stroke( + pattern_rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ); + } + ui.painter().rect_stroke( + cursor_rect, + 5., + egui::Stroke::new(1., egui::Color32::YELLOW), + ); + + response + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 2af506bf..5439f2eb 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -22,13 +22,6 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -use num_traits::FromPrimitive; -use std::fmt::Display; -use strum::IntoEnumIterator; - -use rmxp_types::rpg; -use rmxp_types::NilPadded; - /// Syntax highlighter pub mod syntax_highlighting; /// Toasts to be displayed for errors, information, etc. @@ -40,216 +33,37 @@ mod command_view; pub use command_view::CommandView; -pub use tilemap::{Tilemap, TilemapDef}; pub use toasts::Toasts; pub use top_bar::TopBar; /// The tilemap. -mod tilemap { - use rmxp_types::rpg; - - cfg_if::cfg_if! { - if #[cfg(feature = "generic-tilemap")] { - mod generic_tilemap; - pub use generic_tilemap::Tilemap; - } else { - mod hardware_tilemap; - pub use hardware_tilemap::Tilemap; - } - } - - /// A trait defining how a tilemap should function. - pub trait TilemapDef: Sized { - /// Create a new tilemap. - fn new(id: i32) -> Result; - - /// Display the tilemap. - fn ui( - &mut self, - ui: &mut egui::Ui, - map: &rpg::Map, - cursor_pos: &mut egui::Pos2, - toggled_layers: &[bool], - selected_layer: usize, - dragging_event: bool, - ) -> egui::Response; - - /// Display the tile picker. - fn tilepicker(&self, ui: &mut egui::Ui, selected_tile: &mut i16); - } -} - -// btw there's a buncha places this could be used -// uhh in event edit there's an array of strings that gets itered over to do what this does lol -// TODO: Replace dropbox mechanism in event edit with this method -pub trait Enumeration: Display + FromPrimitive + IntoEnumIterator {} -impl Enumeration for T where T: Display + FromPrimitive + IntoEnumIterator {} -pub struct EnumMenuButton -where - T: Enumeration, - F: FnMut(T), -{ - current_value: i32, - _enumeration: T, - on_select: F, -} -impl EnumMenuButton { - pub fn new(current_value: i32, enumeration: T, on_select: F) -> Self { - Self { - current_value, - _enumeration: enumeration, - on_select, - } - } -} -impl egui::Widget for EnumMenuButton { - fn ui(mut self, ui: &mut egui::Ui) -> egui::Response { - ui.menu_button(T::from_i32(self.current_value).unwrap().to_string(), |ui| { - for enumeration_item in T::iter() { - if ui.button(enumeration_item.to_string()).clicked() { - (self.on_select)(enumeration_item); - ui.close_menu(); - } - } - }) - .response - } -} - -pub trait NilPaddedStructure: Default { - fn name(&self) -> String; - fn id(&self) -> i32; - - fn set_name(&mut self, new_name: impl Into); -} -impl NilPaddedStructure for rpg::Animation { - fn id(&self) -> i32 { - self.id - } - fn name(&self) -> String { - self.name.clone() - } - - fn set_name(&mut self, new_name: impl Into) { - self.name = new_name.into(); - } -} -impl NilPaddedStructure for rpg::CommonEvent { - fn id(&self) -> i32 { - self.id as i32 - } - fn name(&self) -> String { - self.name.clone() - } +mod map_view; +pub use map_view::{MapView, SelectedLayer}; +mod tilepicker; +pub use tilepicker::{SelectedTile, Tilepicker}; - fn set_name(&mut self, new_name: impl Into) { - self.name = new_name.into(); - } +pub struct EnumMenuButton<'e, T> { + current_value: &'e mut T, + id: egui::Id, } -pub struct NilPaddedMenu<'nil, T> -where - T: NilPaddedStructure, -{ - pub id: &'nil mut i32, - pub structure_list: &'nil NilPadded, - - default_structure: T, -} -impl<'nil, T> NilPaddedMenu<'nil, T> -where - T: NilPaddedStructure, -{ - pub fn new(id: &'nil mut i32, structure_list: &'nil NilPadded) -> Self { - let mut structure = T::default(); - structure.set_name("(None)"); - Self { - id, - structure_list, - - default_structure: structure, - } - } -} -impl<'nil, T> egui::Widget for NilPaddedMenu<'nil, T> -where - T: NilPaddedStructure, -{ - fn ui(self, ui: &mut egui::Ui) -> egui::Response { - #[allow(clippy::cast_sign_loss)] - let id = *self.id as usize; - ui.menu_button( - if id == 0 { - String::from("(None)") - } else { - self.structure_list - .get(id.checked_sub(1).unwrap_or(id)) - .unwrap_or(&self.default_structure) - .name() - }, - |ui| { - egui::ScrollArea::both().max_height(600.).show_rows( - ui, - ui.text_style_height(&egui::TextStyle::Body), - self.structure_list.len(), - |ui, rows| { - ui.selectable_value(self.id, -1, "000: (None)"); - for (item_id, item) in self - .structure_list - .iter() - .enumerate() - .filter(|(element, _)| rows.contains(element)) - { - let item_id = item_id as i32 + 1; - if ui - .selectable_value( - self.id, - item_id, - format!("{:0>3}: {}", item_id, item.name()), - ) - .clicked() - { - ui.close_menu(); - } - } - }, - ) - }, - ) - .response +impl<'e, T> EnumMenuButton<'e, T> { + pub fn new(current_value: &'e mut T, id: egui::Id) -> Self { + Self { current_value, id } } } -/// Wrapper for an `egui` button with callback support. -pub struct CallbackButton<'callback> { - btn: egui::Button, - on_click: Option>, -} -impl<'callback> CallbackButton<'callback> { - pub fn new(text: impl Into) -> Self { - Self { - btn: egui::Button::new(text), - on_click: None, - } - } - - #[must_use] - pub fn on_click(mut self, new_on_click_callback: impl FnOnce() + 'callback) -> Self { - self.on_click = Some(Box::new(new_on_click_callback)); - self - } -} -impl<'callback> egui::Widget for CallbackButton<'callback> { +impl<'e, T: ToString + PartialEq + strum::IntoEnumIterator> egui::Widget for EnumMenuButton<'e, T> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { - let response = self.btn.ui(ui); - - if let Some(on_click) = self.on_click { - if response.clicked() { - on_click(); - } - } - - response + egui::ComboBox::from_id_source(self.id) + .selected_text(self.current_value.to_string()) + .show_ui(ui, |ui| { + for variant in T::iter() { + let text = variant.to_string(); + ui.selectable_value(self.current_value, variant, text); + } + }) + .response } } diff --git a/src/components/syntax_highlighting.rs b/src/components/syntax_highlighting.rs index 58f26d31..2f02111b 100644 --- a/src/components/syntax_highlighting.rs +++ b/src/components/syntax_highlighting.rs @@ -63,7 +63,15 @@ pub fn highlight(ctx: &egui::Context, theme: CodeTheme, code: &str, language: &s }) } -#[derive(Clone, Copy, Hash, PartialEq, serde::Deserialize, serde::Serialize)] +#[derive( + Clone, + Copy, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + Debug +)] enum SyntectTheme { Base16EightiesDark, Base16MochaDark, @@ -125,7 +133,15 @@ impl SyntectTheme { } } -#[derive(Clone, Copy, Hash, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive( + Clone, + Copy, + Hash, + PartialEq, + serde::Serialize, + serde::Deserialize, + Debug +)] pub struct CodeTheme { dark_mode: bool, @@ -180,7 +196,7 @@ impl CodeTheme { impl CodeTheme { #[must_use] - pub fn dark() -> Self { + pub const fn dark() -> Self { Self { dark_mode: true, syntect_theme: SyntectTheme::Base16MochaDark, @@ -188,7 +204,7 @@ impl CodeTheme { } #[must_use] - pub fn light() -> Self { + pub const fn light() -> Self { Self { dark_mode: false, syntect_theme: SyntectTheme::SolarizedLight, diff --git a/src/components/tilemap/generic_tilemap.rs b/src/components/tilemap/generic_tilemap.rs deleted file mode 100644 index 78df099c..00000000 --- a/src/components/tilemap/generic_tilemap.rs +++ /dev/null @@ -1,705 +0,0 @@ -// Copyright (C) 2023 Lily Lyons -// -// This file is part of Luminol. -// -// Luminol is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Luminol is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Luminol. If not, see . -// -// Additional permission under GNU GPL version 3 section 7 -// -// If you modify this Program, or any covered work, by linking or combining -// it with Steamworks API by Valve Corporation, containing parts covered by -// terms of the Steamworks API by Valve Corporation, the licensors of this -// Program grant you additional permission to convey the resulting work. - -/* -This file serves as a baseline for how to handle the tilemap. -It's slow and should only be used as a reference for how the tilemap works. -*/ -use egui::Color32; -use egui_extras::RetainedImage; -use itertools::Itertools; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; -use std::time::Instant; - -use egui::{Pos2, Response, Vec2}; - -use crate::state; -use rmxp_types::rpg; - -use super::TilemapDef; - -/// A generic tilemap, implements TilemapDef. -/// Does not use any special optimizations. -#[allow(dead_code)] -pub struct Tilemap { - /// The tilemap pan. - pub pan: Vec2, - /// The scale of the tilemap. - pub scale: f32, - /// Toggle to display the visible region in-game. - pub visible_display: bool, - /// Toggle move route preview - pub move_preview: bool, - ani_idx: i32, - ani_instant: Instant, - textures: Textures, -} - -pub struct Textures { - tileset_tex: Option>, - autotile_texs: Vec>>, - event_texs: HashMap<(String, i32), Option>>, - fog_tex: Option>, - fog_zoom: i32, - pano_tex: Option>, -} - -/// Hardcoded list of tiles from r48 and old python Luminol. -/// There seems to be very little pattern in autotile IDs so this is sadly -/// the best we can do. -const AUTOTILES: [[i32; 4]; 48] = [ - [26, 27, 32, 33], - [4, 27, 32, 33], - [26, 5, 32, 33], - [4, 5, 32, 33], - [26, 27, 32, 11], - [4, 27, 32, 11], - [26, 5, 32, 11], - [4, 5, 32, 11], - [26, 27, 10, 33], - [4, 27, 10, 33], - [26, 5, 10, 33], - [4, 5, 10, 33], - [26, 27, 10, 11], - [4, 27, 10, 11], - [26, 5, 10, 11], - [4, 5, 10, 11], - [24, 25, 30, 31], - [24, 5, 30, 31], - [24, 25, 30, 11], - [24, 5, 30, 11], - [14, 15, 20, 21], - [14, 15, 20, 11], - [14, 15, 10, 21], - [14, 15, 10, 11], - [28, 29, 34, 35], - [28, 29, 10, 35], - [4, 29, 34, 35], - [4, 29, 10, 35], - [38, 39, 44, 45], - [4, 39, 44, 45], - [38, 5, 44, 45], - [4, 5, 44, 45], - [24, 29, 30, 35], - [14, 15, 44, 45], - [12, 13, 18, 19], - [12, 13, 18, 11], - [16, 17, 22, 23], - [16, 17, 10, 23], - [40, 41, 46, 47], - [4, 41, 46, 47], - [36, 37, 42, 43], - [36, 5, 42, 43], - [12, 17, 18, 23], - [12, 13, 42, 43], - [36, 41, 42, 47], - [16, 17, 46, 47], - [12, 17, 42, 47], - [0, 1, 6, 7], -]; - -#[allow(dead_code)] -impl TilemapDef for Tilemap { - fn new(id: i32) -> Result { - let textures = Self::load_data(id)?; - Ok(Self { - pan: Vec2::ZERO, - scale: 100., - visible_display: false, - move_preview: false, - ani_idx: 0, - ani_instant: Instant::now(), - textures, - }) - } - - fn ui( - &mut self, - ui: &mut egui::Ui, - map: &rpg::Map, - cursor_pos: &mut Pos2, - toggled_layers: &[bool], - selected_layer: usize, - dragging_event: bool, - ) -> Response { - // Every 16 frames update autotile animation index - if self.ani_instant.elapsed() >= Duration::from_secs_f32((1. / 60.) * 16.) { - self.ani_instant = Instant::now(); - self.ani_idx += 1; - } - - // Allocate the largest size we can for the tilemap - let canvas_rect = ui.max_rect(); - let canvas_center = canvas_rect.center(); - ui.set_clip_rect(canvas_rect); - - let mut response = ui.allocate_rect(canvas_rect, egui::Sense::click_and_drag()); - - // Handle zoom - if let Some(pos) = response.hover_pos() { - // We need to store the old scale before applying any transformations - let old_scale = self.scale; - let delta = ui.input(|i| i.scroll_delta.y * 5.0); - - // Apply scroll and cap max zoom to 15% - self.scale += delta / 30.; - self.scale = 15.0_f32.max(self.scale); - - // Get the normalized cursor position relative to pan - let pos_norm = (pos - self.pan - canvas_center) / old_scale; - // Offset the pan to the cursor remains in the same place - // Still not sure how the math works out, if it ain't broke don't fix it - self.pan = pos - canvas_center - pos_norm * self.scale; - - // Figure out the tile the cursor is hovering over - let tile_size = (self.scale / 100.) * 32.; - let mut pos_tile = (pos - self.pan - canvas_center) / tile_size - + egui::Vec2::new(map.width as f32 / 2., map.height as f32 / 2.); - // Force the cursor to a tile instead of in-between - pos_tile.x = pos_tile.x.floor().clamp(0.0, map.width as f32 - 1.); - pos_tile.y = pos_tile.y.floor().clamp(0.0, map.height as f32 - 1.); - // Handle input - if selected_layer < map.data.zsize() || dragging_event || response.clicked() { - *cursor_pos = pos_tile.to_pos2(); - } - } - - // Handle pan - let panning_map_view = response.dragged_by(egui::PointerButton::Middle) - || (ui.input(|i| { - i.modifiers.command && response.dragged_by(egui::PointerButton::Primary) - })); - if panning_map_view { - self.pan += response.drag_delta(); - } - - // Handle cursor icon - if panning_map_view { - response = response.on_hover_cursor(egui::CursorIcon::Grabbing); - } else { - response = response.on_hover_cursor(egui::CursorIcon::Grab); - } - - // Determine some values which are relatively constant - let scale = self.scale / 100.; - let tile_size = 32. * scale; - let at_tile_size = 16. * scale; - let canvas_pos = canvas_center + self.pan; - - let xsize = map.data.xsize(); - let ysize = map.data.ysize(); - - let tile_width = self - .textures - .tileset_tex - .as_ref() - .map(|t| 32. / t.width() as f32); - let tile_height = self - .textures - .tileset_tex - .as_ref() - .map(|t| 32. / t.height() as f32); - - let width2 = map.width as f32 / 2.; - let height2 = map.height as f32 / 2.; - - let pos = egui::Vec2::new(width2 * tile_size, height2 * tile_size); - let map_rect = egui::Rect { - min: canvas_pos - pos, - max: canvas_pos + pos, - }; - - // Do we need to render a panorama? - if toggled_layers[map.data.zsize() + 1] { - if let Some(pano_tex) = &self.textures.pano_tex { - // Find the minimum number of panoramas we can fit in the map size (should fit the screen) - let mut pano_repeat = map_rect.size() / (pano_tex.size_vec2() * scale); - // We want to display more not less than we possibly can - pano_repeat.x = pano_repeat.x.ceil(); - pano_repeat.y = pano_repeat.y.ceil(); - - // Iterate through ranges - for y in 0..(pano_repeat.y as usize) { - for x in 0..(pano_repeat.x as usize) { - // Display the panorama - let pano_rect = egui::Rect::from_min_size( - map_rect.min - + egui::vec2( - (pano_tex.width() * x) as f32 * scale, - (pano_tex.height() * y) as f32 * scale, - ), - pano_tex.size_vec2() * scale, - ); - - egui::Image::new( - pano_tex.texture_id(ui.ctx()), - pano_tex.size_vec2() * scale, - ) - .paint_at(ui, pano_rect); - } - } - } - } - // Iterate through all tiles. - for (idx, ele) in map.data.iter().enumerate() { - if *ele < 48 { - continue; - } - - // We grab the x and y through some simple means. - let (x, y, z) = ( - // We reset the x every xsize elements. - idx % xsize, - // We reset the y every ysize elements, but only increment it every xsize elements. - (idx / xsize) % ysize, - // We change the z every xsize * ysize elements. - idx / (xsize * ysize), - ); - - // Is the layer toggled off? - if !toggled_layers[z] { - continue; - } - - // Find tile bounds - let tile_rect = egui::Rect::from_min_size( - map_rect.min + egui::Vec2::new(x as f32 * tile_size, y as f32 * tile_size), - egui::Vec2::splat(tile_size), - ); - - // Do we draw an autotile or regular tile? - if *ele >= 384 { - if let Some(ref tileset_tex) = self.textures.tileset_tex { - // Normalize element - let ele = ele - 384; - - // Calculate UV coordinates - let tile_x = - (ele as usize % (tileset_tex.width() / 32)) as f32 * tile_width.unwrap(); - let tile_y = - (ele as usize / (tileset_tex.width() / 32)) as f32 * tile_height.unwrap(); - - let uv = egui::Rect::from_min_size( - Pos2::new(tile_x, tile_y), - egui::vec2(tile_width.unwrap(), tile_height.unwrap()), - ); - - // Display tile - egui::Image::new(tileset_tex.texture_id(ui.ctx()), tileset_tex.size_vec2()) - .uv(uv) - .paint_at(ui, tile_rect); - } - } else { - // holy shit - // Find what autotile we're displaying - let autotile_id = (ele / 48) - 1; - - if let Some(autotile_tex) = &self.textures.autotile_texs[autotile_id as usize] { - // Get the relative tile size - let tile_width = 16. / autotile_tex.width() as f32; - let tile_height = 16. / autotile_tex.height() as f32; - - // Display each autotile corner (taken from r48) - for s_a in 0..2 { - for s_b in 0..2 { - // Find tile display rectangle - let tile_rect = egui::Rect::from_min_size( - map_rect.min - + egui::Vec2::new( - (x as f32 * tile_size) + (s_a as f32 * at_tile_size), - (y as f32 * tile_size) + (s_b as f32 * at_tile_size), - ), - egui::Vec2::splat(at_tile_size), - ); - - // Calculate tile index - let ti = AUTOTILES[*ele as usize % 48][s_a + (s_b * 2)]; - - // Calculate tile x - let tx = ti % 6; - // Offset by animation amount - let tx_off = (self.ani_idx as usize % (autotile_tex.width() / 96)) * 6; - let tx = (tx + tx_off as i32) as f32 * tile_width; - // Calculate tile y - let ty = (ti / 6) as f32 * tile_height; - - // Find uv - let uv = egui::Rect::from_min_size( - Pos2::new(tx, ty), - egui::vec2(tile_width, tile_height), - ); - - // Display corner - egui::Image::new( - autotile_tex.texture_id(ui.ctx()), - autotile_tex.size_vec2(), - ) - .uv(uv) - .paint_at(ui, tile_rect); - } - } - } - } - } - - // Do we display events? - if *toggled_layers.last().unwrap() { - for (_, event) in map.events.iter() { - // aaaaaaaa - // Get graphic and texture - let graphic = &event.pages[0].graphic; - let Some(tex) =self. textures - .event_texs - .get(&(graphic.character_name.clone(), graphic.character_hue)) - else { - continue; - }; - if let Some(tex) = tex { - // FInd character width and height - let cw = (tex.width() / 4) as f32; - let ch = (tex.height() / 4) as f32; - - // The math here display the character correctly. - // Why it works? Dunno. - let c_rect = egui::Rect::from_min_size( - map_rect.min - + egui::Vec2::new( - (event.x as f32 * tile_size) + ((16. - (cw / 2.)) * scale), - (event.y as f32 * tile_size) + ((32. - ch) * scale), - ), - egui::vec2(cw * scale, ch * scale), - ); - - // Find UV coords. - let cx = (graphic.pattern as f32 * cw) / tex.width() as f32; - let cy = (((graphic.direction - 2) / 2) as f32 * ch) / tex.height() as f32; - - let uv = egui::Rect::from_min_size( - Pos2::new(cx, cy), - egui::vec2(cw / tex.width() as f32, ch / tex.height() as f32), - ); - - // Display the character. - egui::Image::new(tex.texture_id(ui.ctx()), tex.size_vec2()) - .uv(uv) - .paint_at(ui, c_rect); - // Do we need to display a tile instead? - } else if graphic.tile_id.is_positive() { - if let Some(ref tileset_tex) = self.textures.tileset_tex { - // Same logic for tiles. See above. - let tile_rect = egui::Rect::from_min_size( - map_rect.min - + egui::Vec2::new( - event.x as f32 * tile_size, - event.y as f32 * tile_size, - ), - egui::Vec2::splat(tile_size), - ); - - let tile_x = ((graphic.tile_id - 384) as usize % (tileset_tex.width() / 32)) - as f32 - * tile_width.unwrap(); - let tile_y = ((graphic.tile_id - 384) as usize / (tileset_tex.width() / 32)) - as f32 - * tile_height.unwrap(); - - let uv = egui::Rect::from_min_size( - Pos2::new(tile_x, tile_y), - egui::vec2(tile_width.unwrap(), tile_height.unwrap()), - ); - - egui::Image::new(tileset_tex.texture_id(ui.ctx()), tileset_tex.size_vec2()) - .uv(uv) - .paint_at(ui, tile_rect); - } - } - - // Display the event box. - let box_rect = egui::Rect::from_min_size( - map_rect.min - + egui::Vec2::new(event.x as f32 * tile_size, event.y as f32 * tile_size), - egui::Vec2::splat(tile_size), - ); - - ui.painter().rect_stroke( - box_rect, - 5.0, - egui::Stroke::new(1.0, egui::Color32::WHITE), - ); - } - } - - // Display the fog if we should. - // Uses an almost identical method to panoramas with an added scale. - if toggled_layers[map.data.zsize() + 2] { - if let Some(fog_tex) = &self.textures.fog_tex { - let zoom = (self.textures.fog_zoom as f32 / 100.) * scale; - let mut fox_repeat = map_rect.size() / (fog_tex.size_vec2() * zoom); - fox_repeat.x = fox_repeat.x.ceil(); - fox_repeat.y = fox_repeat.y.ceil(); - - for y in 0..(fox_repeat.y as usize) { - for x in 0..(fox_repeat.x as usize) { - let fog_rect = egui::Rect::from_min_size( - map_rect.min - + egui::vec2( - (fog_tex.width() * x) as f32 * zoom, - (fog_tex.height() * y) as f32 * zoom, - ), - fog_tex.size_vec2() * zoom, - ); - - egui::Image::new(fog_tex.texture_id(ui.ctx()), fog_tex.size_vec2() * zoom) - .paint_at(ui, fog_rect); - } - } - } - } - - if self.move_preview { - for (_id, event) in map.events.iter() { - for (page_index, page) in event - .pages - .iter() - .enumerate() - .filter(|(_, p)| p.move_type == 3) - { - let _move_route = &page.move_route; - - let _directions = vec![page.graphic.direction]; - let mut points = vec![egui::pos2(event.x as f32, event.y as f32)]; - // process_move_route(move_route, &mut directions, &mut points); - - points = points - .iter_mut() - .map(|p| { - map_rect.min - + (p.to_vec2() * tile_size) - + egui::Vec2::splat(tile_size / 2.) - }) - .collect(); - - let stroke = egui::Stroke::new( - 1.0, - match page_index { - 0 => Color32::YELLOW, - 1 => Color32::BLUE, - 2 => Color32::WHITE, - 3 => Color32::GREEN, - _ => Color32::RED, - }, - ); - - let mut iter = points.into_iter().peekable(); - while let Some(p) = iter.next() { - if let Some(p2) = iter.peek() { - ui.painter().arrow(p, *p2 - p, stroke) - } - } - } - } - } - - if let Some((direction, _route)) = &map.preview_move_route { - let _directions = vec![*direction]; - let mut points = vec![*cursor_pos]; - // process_move_route(route, &mut directions, &mut points); - - points = points - .iter_mut() - .map(|p| { - map_rect.min + (p.to_vec2() * tile_size) + egui::Vec2::splat(tile_size / 2.) - }) - .collect(); - - let stroke = egui::Stroke::new(1.0, Color32::YELLOW); - - let mut iter = points.into_iter().peekable(); - while let Some(p) = iter.next() { - if let Some(p2) = iter.peek() { - ui.painter().arrow(p, *p2 - p, stroke) - } else { - ui.painter().rect_stroke( - egui::Rect::from_min_size( - p - egui::Vec2::splat(tile_size / 2.), - egui::Vec2::splat(tile_size), - ), - 5.0, - stroke, - ) - } - } - } - - // Outline the map. - ui.painter().rect_stroke( - map_rect, - 5.0, - egui::Stroke::new(3.0, egui::Color32::DARK_GRAY), - ); - - // Do we display the visible region? - if self.visible_display { - // Determine the visible region. - let width2: f32 = (640. / 2.) * scale; - let height2: f32 = (480. / 2.) * scale; - - let pos = egui::Vec2::new(width2, height2); - let visible_rect = egui::Rect { - min: canvas_center - pos, - max: canvas_center + pos, - }; - - // Show the region. - ui.painter().rect_stroke( - visible_rect, - 5.0, - egui::Stroke::new(1.0, egui::Color32::YELLOW), - ); - } - - // Display cursor. - let cursor_rect = egui::Rect::from_min_size( - map_rect.min + (cursor_pos.to_vec2() * tile_size), - Vec2::splat(tile_size), - ); - ui.painter().rect_stroke( - cursor_rect, - 5.0, - egui::Stroke::new(1.0, egui::Color32::YELLOW), - ); - - // Every 16 frames request a repaint. This is for autotile animations. - ui.ctx() - .request_repaint_after(Duration::from_secs_f32((1. / 60.) * 16.)); - - // Return response. - response - } - - fn tilepicker(&self, ui: &mut egui::Ui, selected_tile: &mut i16) { - if let Some(ref tileset_tex) = self.textures.tileset_tex { - let (rect, response) = - ui.allocate_exact_size(tileset_tex.size_vec2(), egui::Sense::click()); - - egui::Image::new(tileset_tex.texture_id(ui.ctx()), rect.size()).paint_at(ui, rect); - - if response.clicked() { - if let Some(pos) = response.interact_pointer_pos() { - let mut pos = (pos - rect.min) / 32.; - pos.x = pos.x.floor(); - pos.y = pos.y.floor(); - *selected_tile = (pos.x + pos.y * 8.) as i16; - } - } - let cursor_x = *selected_tile % 8 * 32; - let cursor_y = *selected_tile / 8 * 32; - ui.painter().rect_stroke( - egui::Rect::from_min_size( - rect.min + egui::vec2(f32::from(cursor_x), f32::from(cursor_y)), - egui::Vec2::splat(32.), - ), - 5.0, - egui::Stroke::new(1.0, egui::Color32::WHITE), - ); - } - } -} -impl Tilemap { - #[allow(unused_variables, unused_assignments)] - fn load_data(id: i32) -> Result { - let state = state!(); - // Load the map. - - let map = state.data_cache.load_map(id)?; - // Get tilesets. - let tilesets = state.data_cache.tilesets(); - - // We subtract 1 because RMXP is stupid and pads arrays with nil to start at 1. - let tileset = &tilesets[map.tileset_id as usize - 1]; - - // Load tileset textures. - let tileset_tex = state - .image_cache - .load_egui_image("Graphics/Tilesets", &tileset.tileset_name) - .ok(); - - // Create an async iter over the autotile textures. - let autotile_texs = tileset - .autotile_names - .iter() - .map(|path| { - state - .image_cache - .load_egui_image("Graphics/Autotiles", path) - .ok() - }) - .collect(); - - // Await all the futures. - - let event_texs = map - .events - .iter() - .map(|(_, e)| { - ( - e.pages[0].graphic.character_name.clone(), - e.pages[0].graphic.character_hue, - ) - }) - .dedup() - .map(|(char_name, hue)| { - let texture = state - .image_cache - .load_egui_image("Graphics/Characters", &char_name) - .ok(); - ((char_name, hue), texture) - }) - .collect(); - - // These two are pretty simple. - let fog_tex = state - .image_cache - .load_egui_image("Graphics/Fogs", &tileset.fog_name) - .ok(); - let fog_zoom = tileset.fog_zoom; - - let pano_tex = state - .image_cache - .load_egui_image("Graphics/Panoramas", &tileset.panorama_name) - .ok(); - - // Finally create and return the struct. - Ok(Textures { - tileset_tex, - autotile_texs, - event_texs, - fog_tex, - fog_zoom, - pano_tex, - }) - } -} diff --git a/src/components/tilemap/hardware_tilemap.rs b/src/components/tilemap/hardware_tilemap.rs deleted file mode 100644 index f57fa09a..00000000 --- a/src/components/tilemap/hardware_tilemap.rs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (C) 2023 Lily Lyons -// -// This file is part of Luminol. -// -// Luminol is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Luminol is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Luminol. If not, see . -// -// Additional permission under GNU GPL version 3 section 7 -// -// If you modify this Program, or any covered work, by linking or combining -// it with Steamworks API by Valve Corporation, containing parts covered by -// terms of the Steamworks API by Valve Corporation, the licensors of this -// Program grant you additional permission to convey the resulting work. - -use std::cell::RefCell; -use std::collections::HashMap; -use std::sync::Arc; - -use eframe::glow::NativeTexture; -use egui::{Pos2, Response, Vec2}; - -use super::TilemapDef; -use crate::data::rmxp_structs::rpg; -use crate::glow::{self, HasContext}; -use crate::UpdateInfo; - -#[allow(dead_code)] -pub struct Textures { - pub tileset_tex: NativeTexture, - pub autotile_texs: Vec>, - pub event_texs: HashMap<(String, i32), Option>, - pub fog_tex: Option, - pub fog_zoom: i32, - pub pano_tex: Option, -} - -pub struct Tilemap { - pub scale: f32, - pub visible_display: bool, - pub pan: Vec2, - vao: glow::NativeVertexArray, - load_data: poll_promise::Promise<()>, -} - -// We only want to create shaders once. This setup allows us to do that. -unsafe fn with_hardware_shaders(gl: Arc, mut f: impl FnMut(glow::NativeProgram)) { - thread_local! { - static SHADERS: RefCell> = RefCell::new(None) - } - SHADERS.with(|s| { - f(*s.borrow_mut().get_or_insert_with(|| { - let vert_source = r#" - #version 330 core - layout (location = 0) in vec3 aPos; - layout (location = 1) in vec3 aColor; - - out vec3 ourColor; // output a color to the fragment shader - - void main() - { - gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); - ourColor = aColor; - } - "#; - - let vert = gl.create_shader(glow::VERTEX_SHADER).unwrap(); - gl.shader_source(vert, vert_source); - gl.compile_shader(vert); - - if !gl.get_shader_compile_status(vert) { - println!("SHDER LOG: {}", gl.get_shader_info_log(vert)); - } - - let frag_source = r#" - #version 330 core - out vec4 FragColor; - - in vec3 ourColor; - - void main() - { - FragColor = vec4(ourColor, 1.0f); - } - "#; - - let frag = gl.create_shader(glow::FRAGMENT_SHADER).unwrap(); - gl.shader_source(frag, frag_source); - gl.compile_shader(frag); - - if !gl.get_shader_compile_status(frag) { - println!("SHDER LOG: {}", gl.get_shader_info_log(frag)); - } - - let program = gl.create_program().unwrap(); - gl.attach_shader(program, vert); - gl.attach_shader(program, frag); - gl.link_program(program); - - gl.delete_shader(vert); - gl.delete_shader(frag); - - program - })); - }) -} - -#[allow(dead_code)] -impl TilemapDef for Tilemap { - fn new(info: &'static UpdateInfo, id: i32) -> Self { - let vao = unsafe { - let gl = info.gl.clone(); - - let vao = gl.create_vertex_array().unwrap(); - gl.bind_vertex_array(Some(vao)); - - let vertices: [f32; 18] = [ - // positions // colors - 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, // bottom right - -0.5, -0.5, 0.0, 0.0, 1.0, 0.0, // bottom left - 0.0, 0.5, 0.0, 0.0, 0.0, 1.0, // top - ]; - - let vert_buffer = gl.create_buffer().unwrap(); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vert_buffer)); - gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - vertices.as_ptr() as _, - vertices.len() * std::mem::size_of::(), - ), // Holy *shit* this is BAD. we are converting a slice of f32s to bytes by reading their memory AS bytes. - // At least rust makes this behavior obvious rather than C's void*... - glow::STATIC_DRAW, - ); - - gl.vertex_attrib_pointer_f32( - 0, - 3, - glow::FLOAT, - false, - 6 * std::mem::size_of::() as i32, - 0, - ); // We have 3 floats per 'index'? so that's why we multiply 3 by the sizeof float. - gl.enable_vertex_attrib_array(0); - - gl.vertex_attrib_pointer_f32( - 1, - 3, - glow::FLOAT, - false, - 6 * std::mem::size_of::() as i32, - 3 * std::mem::size_of::() as i32, // We need to offset it by 3 floats since our color is after positions - ); // We have 3 floats per 'index'? so that's why we multiply 3 by the sizeof float. - gl.enable_vertex_attrib_array(1); - - vao - }; - - Self { - scale: 100., - visible_display: false, - pan: Vec2::ZERO, - vao, - load_data: poll_promise::Promise::spawn_local(async move { - Self::load_data(info, id).await.unwrap() - }), - } - } - - #[allow(unused_variables)] - fn ui( - &mut self, - ui: &mut egui::Ui, - map: &rpg::Map, - cursor_pos: &mut Pos2, - toggled_layers: &[bool], - ) -> Response { - let canvas_rect = ui.max_rect(); - let canvas_center = canvas_rect.center(); - - let response = ui.allocate_rect(canvas_rect, egui::Sense::click_and_drag()); - - let vao = self.vao; - ui.painter().add(egui::PaintCallback { - rect: canvas_rect, - callback: std::sync::Arc::new(eframe::egui_glow::CallbackFn::new( - move |_info, painter| unsafe { - with_hardware_shaders(painter.gl().clone(), |program| { - let gl = painter.gl().clone(); - - gl.polygon_mode(glow::FRONT_AND_BACK, glow::FILL); - - gl.use_program(Some(program)); - - gl.bind_vertex_array(Some(vao)); - gl.draw_arrays(glow::TRIANGLES, 0, 3); - - gl.polygon_mode(glow::FRONT_AND_BACK, glow::FILL); - }) - }, - )), - }); - - response - } - - fn tilepicker(&self, ui: &mut egui::Ui, selected_tile: &mut i16) { - let (rect, response) = ui.allocate_exact_size(egui::vec2(256., 256.), egui::Sense::click()); // textures.tileset_tex.size_vec2(), egui::Sense::click()); - - if response.clicked() { - if let Some(pos) = response.interact_pointer_pos() { - let mut pos = (pos - rect.min) / 32.; - pos.x = pos.x.floor(); - pos.y = pos.y.floor(); - *selected_tile = (pos.x + pos.y * 8.) as i16; - } - } - - let cursor_x = *selected_tile % 8 * 32; - let cursor_y = *selected_tile / 8 * 32; - ui.painter().rect_stroke( - egui::Rect::from_min_size( - rect.min + egui::vec2(cursor_x as f32, cursor_y as f32), - egui::Vec2::splat(32.), - ), - 5.0, - egui::Stroke::new(1.0, egui::Color32::WHITE), - ); - } - - fn textures_loaded(&self) -> bool { - self.load_data.ready().is_some() - } - - fn load_result(&self) -> Result<(), String> { - Ok(()) - } -} - -impl Tilemap { - async fn load_data(info: &'static UpdateInfo, id: i32) -> Result<(), String> { - let _map = info.data_cache.load_map(&info.filesystem, id).await?; - - Ok(()) - } -} diff --git a/src/components/tilepicker.rs b/src/components/tilepicker.rs new file mode 100644 index 00000000..65ecc5c6 --- /dev/null +++ b/src/components/tilepicker.rs @@ -0,0 +1,195 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +pub use crate::prelude::*; + +use slab::Slab; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct Tilepicker { + pub selected_tiles_left: i16, + pub selected_tiles_top: i16, + pub selected_tiles_right: i16, + pub selected_tiles_bottom: i16, + + drag_origin: Option, + + resources: Arc, + ani_instant: Instant, +} + +#[derive(Debug)] +struct Resources { + tiles: primitives::Tiles, + viewport: primitives::Viewport, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum SelectedTile { + Autotile(i16), + Tile(i16), +} + +impl SelectedTile { + pub fn from_id(id: i16) -> Self { + if id < 384 { + SelectedTile::Autotile(id / 48) + } else { + SelectedTile::Tile(id) + } + } + + pub fn to_id(&self) -> i16 { + match *self { + Self::Autotile(tile) => tile * 48, + Self::Tile(tile) => tile, + } + } +} + +impl Default for SelectedTile { + fn default() -> Self { + SelectedTile::Autotile(0) + } +} + +type ResourcesSlab = Slab>; + +impl Tilepicker { + pub fn new(tileset: &rpg::Tileset) -> Result { + let atlas = state!().atlas_cache.load_atlas(tileset)?; + + let tilepicker_data = (47..(384 + 47)) + .step_by(48) + .chain(384..(atlas.tileset_height as i16 / 32 * 8 + 384)) + .collect_vec(); + let tilepicker_data = Table3::new_data( + 8, + 1 + (atlas.tileset_height / 32) as usize, + 1, + tilepicker_data, + ); + + let viewport = primitives::Viewport::new(glam::Mat4::orthographic_rh( + 0.0, + 256., + atlas.tileset_height as f32 + 32., + 0.0, + -1.0, + 1.0, + )); + + let tiles = primitives::Tiles::new(atlas, &tilepicker_data); + + Ok(Self { + resources: Arc::new(Resources { tiles, viewport }), + ani_instant: Instant::now(), + selected_tiles_left: 0, + selected_tiles_top: 0, + selected_tiles_right: 0, + selected_tiles_bottom: 0, + drag_origin: None, + }) + } + + pub fn get_tile_from_offset(&self, x: i16, y: i16) -> SelectedTile { + let width = self.selected_tiles_right - self.selected_tiles_left + 1; + let height = self.selected_tiles_bottom - self.selected_tiles_top + 1; + let x = self.selected_tiles_left + x.rem_euclid(width); + let y = self.selected_tiles_top + y.rem_euclid(height); + match y { + ..=0 => SelectedTile::Autotile(x), + _ => SelectedTile::Tile(x + (y - 1) * 8 + 384), + } + } + + pub fn ui(&mut self, ui: &mut egui::Ui) -> egui::Response { + if self.ani_instant.elapsed() >= Duration::from_secs_f32((1. / 60.) * 16.) { + self.ani_instant = Instant::now(); + self.resources.tiles.autotiles.inc_ani_index(); + } + ui.ctx().request_repaint_after(Duration::from_millis(16)); + + let (canvas_rect, response) = ui.allocate_exact_size( + egui::vec2(256., self.resources.tiles.atlas.tileset_height as f32 + 32.), + egui::Sense::click_and_drag(), + ); + + let resources = self.resources.clone(); + let prepare_id = Arc::new(OnceCell::new()); + let paint_id = prepare_id.clone(); + + ui.painter().add(egui::PaintCallback { + rect: canvas_rect, + callback: Arc::new( + egui_wgpu::CallbackFn::new() + .prepare(move |_, _, _encoder, paint_callback_resources| { + let res_hash: &mut ResourcesSlab = paint_callback_resources + .entry() + .or_insert_with(Default::default); + let id = res_hash.insert(resources.clone()); + prepare_id.set(id).expect("resources id already set?"); + + vec![] + }) + .paint(move |_info, render_pass, paint_callback_resources| { + let res_hash: &ResourcesSlab = paint_callback_resources.get().unwrap(); + let id = paint_id.get().copied().expect("resources id is unset"); + let resources = &res_hash[id]; + let Resources { + tiles, viewport, .. + } = resources.as_ref(); + + tiles.draw(viewport, &[true], None, render_pass); + }), + ), + }); + + let rect = egui::Rect::from_x_y_ranges( + (self.selected_tiles_left * 32) as f32..=((self.selected_tiles_right + 1) * 32) as f32, + (self.selected_tiles_top * 32) as f32..=((self.selected_tiles_bottom + 1) * 32) as f32, + ) + .translate(canvas_rect.min.to_vec2()); + ui.painter() + .rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); + + let Some(pos) = response.interact_pointer_pos() else { + return response; + }; + let pos = ((pos - canvas_rect.min) / 32.).to_pos2(); + + if response.dragged_by(egui::PointerButton::Primary) { + let drag_origin = if let Some(drag_origin) = self.drag_origin { + drag_origin + } else { + self.drag_origin = Some(pos); + pos + }; + let rect = egui::Rect::from_two_pos(drag_origin, pos); + self.selected_tiles_left = (rect.left() as i16).max(0); + self.selected_tiles_right = (rect.right() as i16).min(7); + self.selected_tiles_top = (rect.top() as i16).max(0); + self.selected_tiles_bottom = + (rect.bottom() as i16).min(self.resources.tiles.atlas.tileset_height as i16 / 32); + } else { + self.drag_origin = None; + } + + response + } +} diff --git a/src/components/top_bar.rs b/src/components/top_bar.rs index eed9dd9e..d5054a56 100644 --- a/src/components/top_bar.rs +++ b/src/components/top_bar.rs @@ -30,19 +30,14 @@ use crate::Pencil; #[derive(Default)] pub struct TopBar { open_project_promise: Option>>, - egui_settings_open: bool, + fullscreen: bool, } impl TopBar { /// Display the top bar. #[allow(unused_variables)] - pub fn ui( - &mut self, - ui: &mut egui::Ui, - style: &mut Arc, - frame: &mut eframe::Frame, - ) { + pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { let state = state!(); egui::widgets::global_dark_light_mode_switch(ui); @@ -62,7 +57,7 @@ impl TopBar { ui.menu_button("File", |ui| { ui.label(if let Some(path) = state.filesystem.project_path() { - format!("Current project:\n{}", path.display()) + format!("Current project:\n{}", path) } else { "No project open".to_string() }); @@ -77,13 +72,14 @@ impl TopBar { ui.add_enabled_ui(state.filesystem.project_loaded(), |ui| { if ui.button("Project Config").clicked() { - state.windows.add_window(config::Window {}); + state.windows.add_window(config_window::Window {}); } if ui.button("Close Project").clicked() { - state.filesystem.unload_project(); state.windows.clean_windows(); - state.tabs.clean_tabs(|t| t.requires_filesystem()); + state.tabs.clean_tabs(|t| !t.requires_filesystem()); + state.audio.clear_sinks(); // audio loads files borrows from the filesystem. unloading while they are playing is a crash + state.filesystem.unload_project(); } save_project |= ui.button("Save Project").clicked(); @@ -108,56 +104,16 @@ impl TopBar { ui.separator(); - ui.menu_button("Appearance", |ui| { - // Or these together so if one OR the other is true the window shows. - self.egui_settings_open = - ui.button("Egui Settings").clicked() || self.egui_settings_open; - - ui.menu_button("Catppuccin theme", |ui| { - if ui.button("Frappe").clicked() { - catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::FRAPPE); - } - if ui.button("Latte").clicked() { - catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::LATTE); - } - if ui.button("Macchiato").clicked() { - catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::MACCHIATO); - } - if ui.button("Mocha").clicked() { - catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::MOCHA); - } - - *style = ui.ctx().style(); - }); - - let theme = &mut state.saved_state.borrow_mut().theme; - ui.menu_button("Code Theme", |ui| { - theme.ui(ui); - - ui.label("Code sample"); - ui.label(syntax_highlighting::highlight( - ui.ctx(), - *theme, - r#" - class Foo < Array - end - def bar(baz) - end - print 1, 2.0 - puts [0x3, :4, '5'] - "#, - "rb", - )); - }); + ui.menu_button("Edit", |ui| { + // + if ui.button("Preferences").clicked() { + state + .windows + .add_window(global_config_window::Window::default()) + } - if ui - .button("Clear Loaded Textures") - .on_hover_text( - "You may need to reopen maps/windows for any changes to take effect.", - ) - .clicked() - { - state.image_cache.clear(); + if ui.button("Appearance").clicked() { + state.windows.add_window(appearance::Window::default()) } }); @@ -192,12 +148,14 @@ impl TopBar { ui.separator(); ui.menu_button("Help", |ui| { + ui.button("Contents").clicked(); + if ui.button("About...").clicked() { state.windows.add_window(about::Window::default()); }; + }); - ui.separator(); - + ui.menu_button("Debug", |ui| { if ui.button("Egui Inspection").clicked() { state.windows.add_window(misc::EguiInspection::default()); } @@ -209,6 +167,12 @@ impl TopBar { let mut debug_on_hover = ui.ctx().debug_on_hover(); ui.toggle_value(&mut debug_on_hover, "Debug on hover"); ui.ctx().set_debug_on_hover(debug_on_hover); + + ui.separator(); + + if ui.button("Filesystem Debug").clicked() { + state.windows.add_window(misc::FilesystemDebug::default()); + } }); ui.separator(); @@ -257,15 +221,6 @@ impl TopBar { ui.selectable_value(&mut toolbar.pencil, brush, brush.to_string()); } - let ctx = ui.ctx(); - // Because style_ui makes a new style, AND we can't pass the style to a dedicated window, we handle the logic here. - egui::Window::new("Egui Settings") - .open(&mut self.egui_settings_open) - .show(ui.ctx(), |ui| { - ctx.style_ui(ui); - *style = ctx.style(); - }); - if open_project { self.open_project_promise = Some(Promise::spawn_local( state.filesystem.spawn_project_file_picker(), @@ -274,7 +229,7 @@ impl TopBar { if save_project { state.toasts.info("Saving project..."); - match state.filesystem.save_cached() { + match state.data_cache.save() { Ok(_) => state.toasts.info("Saved project sucessfully!"), Err(e) => state.toasts.error(e), } diff --git a/src/project/command_db.rs b/src/config/command_db.rs similarity index 77% rename from src/project/command_db.rs rename to src/config/command_db.rs index b888b289..d0d47172 100644 --- a/src/project/command_db.rs +++ b/src/config/command_db.rs @@ -26,15 +26,27 @@ use command_lib::CommandDescription; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use super::config::RMVer; +use super::RMVer; static XP_DEFAULT: Lazy> = Lazy::new(|| { - ron::from_str(include_str!("xp_default.ron")).expect( + ron::from_str(include_str!("commands/xp.ron")).expect( "failed to statically load the default commands for rpg maker xp. please report this bug", ) }); -#[derive(Deserialize, Serialize)] +static VX_DEFAULT: Lazy> = Lazy::new(|| { + ron::from_str(include_str!("commands/vx.ron")).expect( + "failed to statically load the default commands for rpg maker vx. please report this bug", + ) +}); + +static ACE_DEFAULT: Lazy> = Lazy::new(|| { + ron::from_str(include_str!("commands/ace.ron")).expect( + "failed to statically load the default commands for rpg maker vx ace. please report this bug", + ) +}); + +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct CommandDB { /// Default commands default: Vec, @@ -42,20 +54,13 @@ pub struct CommandDB { pub user: Vec, } -impl Default for CommandDB { - fn default() -> Self { - Self { - default: XP_DEFAULT.clone(), - user: vec![], - } - } -} - impl CommandDB { pub fn new(ver: RMVer) -> Self { Self { default: match ver { RMVer::XP => &*XP_DEFAULT, + RMVer::VX => &*VX_DEFAULT, + RMVer::Ace => &*ACE_DEFAULT, } .clone(), user: vec![], diff --git a/src/project/xp_default.ron b/src/config/commands/ace.ron similarity index 100% rename from src/project/xp_default.ron rename to src/config/commands/ace.ron diff --git a/src/config/commands/vx.ron b/src/config/commands/vx.ron new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/src/config/commands/vx.ron @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/config/commands/xp.ron b/src/config/commands/xp.ron new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/src/config/commands/xp.ron @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/saved_state.rs b/src/config/global.rs similarity index 77% rename from src/saved_state.rs rename to src/config/global.rs index a55318d5..723ad595 100644 --- a/src/saved_state.rs +++ b/src/config/global.rs @@ -23,23 +23,32 @@ // Program grant you additional permission to convey the resulting work. use crate::prelude::*; +use std::collections::HashMap; use std::collections::VecDeque; /// The state saved by Luminol between sessions. -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(default)] -pub struct SavedState { +pub struct Config { /// Recently open projects. pub recent_projects: VecDeque, /// The current code theme pub theme: syntax_highlighting::CodeTheme, + pub rtp_paths: HashMap, } -impl Default for SavedState { +impl Default for Config { fn default() -> Self { - SavedState { - recent_projects: VecDeque::with_capacity(10), - theme: syntax_highlighting::CodeTheme::default(), + Self::new() + } +} + +impl Config { + pub fn new() -> Self { + Self { + recent_projects: VecDeque::new(), + theme: syntax_highlighting::CodeTheme::dark(), + rtp_paths: HashMap::new(), } } } diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..4ae5b30f --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,245 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +mod command_db; +pub mod global; +pub mod project; + +pub use command_db::CommandDB; +pub use global::Config; + +#[derive(Default, Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub enum Project { + #[default] + Unloaded, + Loaded { + config: project::Config, + command_db: CommandDB, + game_ini: ini::Ini, + }, +} + +impl Project { + pub fn load() -> Result<(), String> { + let filesystem = &state!().filesystem; + + if !filesystem.exists(".luminol").map_err(|e| e.to_string())? { + filesystem + .create_dir(".luminol") + .map_err(|e| e.to_string())?; + } + + let config = match filesystem + .read_to_string(".luminol/config") + .ok() + .and_then(|s| ron::from_str(&s).ok()) + { + Some(c) => c, + None => { + let Some(editor_ver) = filesystem.detect_rm_ver() else { + return Err("Unable to detect RPG Maker version".to_string()); + }; + let config = project::Config { + editor_ver, + ..Default::default() + }; + filesystem + .write(".luminol/config", ron::to_string(&config).unwrap()) + .map_err(|e| e.to_string())?; + config + } + }; + + let command_db = match filesystem + .read_to_string(".luminol/commands") + .ok() + .and_then(|s| ron::from_str(&s).ok()) + { + Some(c) => c, + None => { + let command_db = CommandDB::new(config.editor_ver); + filesystem + .write(".luminol/commands", ron::to_string(&command_db).unwrap()) + .map_err(|e| e.to_string())?; + command_db + } + }; + + let game_ini = match filesystem + .read_to_string("Game.ini") + .ok() + .and_then(|i| ini::Ini::load_from_str_noescape(&i).ok()) + { + Some(i) => i, + None => { + let mut ini = ini::Ini::new(); + ini.with_section(Some("Game")) + .set("Library", "RGSS104E.dll") + .set("Scripts", &config.scripts_path) + .set("Title", &config.project_name) + .set("RTP1", "") + .set("RTP2", "") + .set("RTP3", ""); + + ini + } + }; + + *PROJECT.borrow_mut() = Project::Loaded { + config, + command_db, + game_ini, + }; + + Ok(()) + } + + pub fn save() -> Result<(), String> { + match &*PROJECT.borrow() { + Project::Unloaded => return Err("Project not loaded".to_string()), + Project::Loaded { + config, + command_db, + game_ini, + } => { + state!() + .filesystem + .write( + ".luminol/commands", + ron::to_string(command_db).map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + + state!() + .filesystem + .write( + ".luminol/config", + ron::to_string(config).map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + + let mut ini_file = state!() + .filesystem + .open_file("Game.ini", filesystem::OpenFlags::Create) + .map_err(|e| e.to_string())?; + game_ini + .write_to(&mut ini_file) + .map_err(|e| e.to_string())?; + } + } + + Ok(()) + } +} + +pub static PROJECT: AtomicRefCell = AtomicRefCell::new(Project::Unloaded); + +#[macro_export] +macro_rules! project_config { + () => {{ + AtomicRefMut::map($crate::config::PROJECT.borrow_mut(), |c| match c { + $crate::config::Project::Unloaded => panic!("Project not loaded"), + $crate::config::Project::Loaded { config, .. } => config, + }) + }}; +} + +#[macro_export] +macro_rules! command_db { + () => { + AtomicRefMut::map($crate::config::PROJECT.borrow_mut(), |c| match c { + $crate::config::Project::Unloaded => panic!("Project not loaded"), + $crate::config::Project::Loaded { command_db, .. } => command_db, + }) + }; +} + +#[macro_export] +macro_rules! game_ini { + () => { + AtomicRefMut::map($crate::config::PROJECT.borrow_mut(), |c| match c { + $crate::config::Project::Unloaded => panic!("Project not loaded"), + $crate::config::Project::Loaded { game_ini, .. } => game_ini, + }) + }; +} + +pub static GLOBAL: Lazy> = + Lazy::new(|| AtomicRefCell::new(global::Config::new())); + +#[macro_export] +macro_rules! global_config { + () => { + $crate::config::GLOBAL.borrow_mut() + }; +} + +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + serde::Serialize, + serde::Deserialize, + strum::EnumIter, + strum::Display, + Debug +)] +#[allow(missing_docs)] +pub enum RGSSVer { + #[strum(to_string = "ModShot")] + ModShot, + #[strum(to_string = "mkxp-oneshot")] + MKXPOneShot, + #[strum(to_string = "rsgss")] + RSGSS, + #[strum(to_string = "mkxp")] + MKXP, + #[strum(to_string = "mkxp-freebird")] + MKXPFreebird, + #[strum(to_string = "mkxp-z")] + MKXPZ, + #[strum(to_string = "Stock RGSS1")] + RGSS1, +} + +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + serde::Serialize, + serde::Deserialize, + strum::EnumIter, + strum::Display, + Default, + Debug +)] +#[allow(missing_docs)] +pub enum RMVer { + #[default] + #[strum(to_string = "RPG Maker XP")] + XP = 1, + #[strum(to_string = "RPG Maker VX")] + VX = 2, + #[strum(to_string = "RPG Maker VX Ace")] + Ace = 3, +} diff --git a/src/project/config.rs b/src/config/project.rs similarity index 64% rename from src/project/config.rs rename to src/config/project.rs index 298e964b..cd0bf1a4 100644 --- a/src/project/config.rs +++ b/src/config/project.rs @@ -22,23 +22,24 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use super::{RGSSVer, RMVer}; use serde::{Deserialize, Serialize}; -use strum::EnumIter; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(default)] /// Local luminol project config #[allow(missing_docs)] -pub struct LocalConfig { +pub struct Config { pub project_name: String, pub scripts_path: String, pub use_ron: bool, pub rgss_ver: RGSSVer, pub editor_ver: RMVer, pub playtest_exe: String, + pub prefer_rgssad: bool, } -impl Default for LocalConfig { +impl Default for Config { fn default() -> Self { Self { project_name: String::new(), @@ -47,37 +48,7 @@ impl Default for LocalConfig { rgss_ver: RGSSVer::RGSS1, editor_ver: RMVer::XP, playtest_exe: "game".to_string(), + prefer_rgssad: false, } } } - -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumIter, strum::Display)] -#[allow(missing_docs)] -pub enum RGSSVer { - #[strum(to_string = "ModShot")] - ModShot, - #[strum(to_string = "mkxp-oneshot")] - MKXPOneShot, - #[strum(to_string = "rsgss")] - RSGSS, - #[strum(to_string = "mkxp")] - MKXP, - #[strum(to_string = "mkxp-freebird")] - MKXPFreebird, - #[strum(to_string = "mkxp-z")] - MKXPZ, - #[strum(to_string = "Stock RGSS1")] - RGSS1, -} - -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumIter, strum::Display, Default)] -#[allow(missing_docs)] -pub enum RMVer { - #[default] - #[strum(to_string = "RPG Maker XP")] - XP, - // #[strum(to_string = "RPG Maker VX")] - // VX, - // #[strum(to_string = "RPG Maker VX Ace")] - // Ace, -} diff --git a/src/filesystem.rs b/src/filesystem.rs deleted file mode 100644 index ce5853d1..00000000 --- a/src/filesystem.rs +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (C) 2023 Lily Lyons -// -// This file is part of Luminol. -// -// Luminol is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Luminol is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Luminol. If not, see . -// -// Additional permission under GNU GPL version 3 section 7 -// -// If you modify this Program, or any covered work, by linking or combining -// it with Steamworks API by Valve Corporation, containing parts covered by -// terms of the Steamworks API by Valve Corporation, the licensors of this -// Program grant you additional permission to convey the resulting work. - -pub use crate::prelude::*; -use std::sync::atomic::{AtomicBool, Ordering}; - -/// Native filesystem implementation. -#[derive(Default)] -pub struct Filesystem { - project_path: RwLock>, - loading_project: AtomicBool, -} - -impl Filesystem { - /// Unload the currently loaded project. - /// Does nothing if none is open. - pub fn unload_project(&self) { - *self.project_path.write() = None; - } - - /// Is there a project loaded? - pub fn project_loaded(&self) -> bool { - self.project_path.read().is_some() && !self.loading_project.load(Ordering::Relaxed) - } - - /// Get the project path. - pub fn project_path(&self) -> Option { - self.project_path.read().clone() - } - - /// Get the directory children of a path. - pub fn dir_children(&self, path: impl AsRef) -> Result { - // I am too lazy to make this actually . - // It'd take an external library or some hacking that I'm not up for currently. - std::fs::read_dir( - self.project_path - .read() - .as_ref() - .ok_or_else(|| "Project not open".to_string())? - .join(path), - ) - .map_err(|e| e.to_string()) - } - - pub fn dir_children_strings(&self, path: impl AsRef) -> Result, String> { - std::fs::read_dir( - self.project_path - .read() - .as_ref() - .ok_or_else(|| "Project not open".to_string())? - .join(path), - ) - .map_err(|e| e.to_string()) - .map(|rd| { - rd.into_iter() - .map(|e| e.unwrap().file_name().into_string().unwrap()) - .collect() - }) - } - - /// Read a data file and deserialize it with RON (rusty object notation) - /// In the future this will take an optional parameter (type) to set the loading method. - /// (Options would be Marshal, RON, Lumina) - pub fn read_data(&self, path: impl AsRef) -> Result - where - T: serde::de::DeserializeOwned, - { - let path = self - .project_path - .read() - .as_ref() - .ok_or_else(|| "Project not open".to_string())? - .join(path); - - let data = std::fs::read(&path).map_err(|e| e.to_string())?; - - // let de = &mut alox_48::Deserializer::new(&data).unwrap(); - // let result = serde_path_to_error::deserialize(de); - // - // result.map_err(|e| format!("{}: {:?}", e.path(), e.inner())) - alox_48::from_bytes(&data).map_err(|e| format!("Loading {path:?}: {e}")) - } - - /// Read bytes from a file. - pub fn read_bytes(&self, provided_path: impl AsRef) -> Result, String> { - let path = self - .project_path - .read() - .as_ref() - .ok_or_else(|| "Project not open".to_string())? - .join(provided_path); - std::fs::read(&path).map_err(|e| format!("Loading {path:?}: {e}")) - } - - pub fn save_data(&self, path: impl AsRef, data: impl AsRef<[u8]>) -> Result<(), String> { - let path = self - .project_path - .read() - .as_ref() - .ok_or_else(|| "Project not open".to_string())? - .join(path); - - std::fs::write(path, data).map_err(|e| e.to_string()) - } - - /// Check if file path exists - pub fn path_exists(&self, path: impl AsRef) -> bool { - let path = self.project_path.read().as_ref().unwrap().join(path); - - std::fs::metadata(path).is_ok() - } - - /// Save all cached files. An alias for [`DataCache::save`]; - pub fn save_cached(&self) -> Result<(), String> { - state!().data_cache.save(self) - } - - pub fn try_open_project(&self, path: impl AsRef) -> Result<(), String> { - let mut path = path.as_ref().to_path_buf(); - let original_path = path.to_string_lossy().to_string(); - - path.pop(); // Pop off filename - - *self.project_path.write() = Some(path); - self.loading_project.store(true, Ordering::Relaxed); - - state!().data_cache.load().map_err(|e| { - *self.project_path.write() = None; - self.loading_project.store(false, Ordering::Relaxed); - e - })?; - - self.loading_project.store(false, Ordering::Relaxed); - - { - let projects = &mut state!().saved_state.borrow_mut().recent_projects; - - *projects = projects - .iter() - .filter_map(|p| { - if *p == original_path { - None - } else { - Some(p.clone()) - } - }) - .collect(); - projects.push_front(original_path); - projects.truncate(10); - } - - std::env::set_current_dir(self.project_path().unwrap()).map_err(|e| e.to_string())?; - - Ok(()) - } - - /// Try to open a project. - pub async fn spawn_project_file_picker(&self) -> Result<(), String> { - if let Some(path) = rfd::AsyncFileDialog::default() - .add_filter("project file", &["rxproj", "lumproj"]) - .pick_file() - .await - { - self.try_open_project(path.path()) - } else { - Err("Cancelled loading project".to_string()) - } - } - - /// Create a directory at the specified path. - pub fn create_directory(&self, path: impl AsRef) -> Result<(), String> { - let path = self - .project_path - .read() - .as_ref() - .ok_or_else(|| "Project not open".to_string())? - .join(path); - - std::fs::create_dir(path).map_err(|e| e.to_string()) - } - - /// Try to create a project. - pub async fn try_create_project(&self, name: String, rgss_ver: RGSSVer) -> Result<(), String> { - if let Some(path) = rfd::AsyncFileDialog::default().pick_folder().await { - let path: PathBuf = path.into(); - let path = path.join(name.clone()); - - self.create_project(name.clone(), path, rgss_ver) - .map_err(|e| { - *self.project_path.write() = None; - e - })?; - - { - let projects = &mut state!().saved_state.borrow_mut().recent_projects; - - let path = self.project_path().unwrap().display().to_string(); - *projects = projects - .iter() - .filter_map(|p| if *p != path { Some(p.clone()) } else { None }) - .collect(); - projects.push_front(path); - projects.truncate(10); - } - - self.save_data(format!("{name}.lumproj"), "") - } else { - Err("Cancelled opening a folder".to_owned()) - } - } - - pub fn create_project( - &self, - name: String, - path: PathBuf, - rgss_ver: RGSSVer, - ) -> Result<(), String> { - *self.project_path.write() = Some(path); - self.create_directory("")?; - - if !self.dir_children(".")?.count() == 0 { - return Err("Directory not empty".to_string()); - } - - self.create_directory("Data")?; - - state!().data_cache.setup_defaults(); - { - let mut config = state!().data_cache.config(); - config.rgss_ver = rgss_ver; - config.project_name = name; - } - - self.save_cached()?; - - self.create_directory("Audio")?; - self.create_directory("Audio/BGM")?; - self.create_directory("Audio/BGS")?; - self.create_directory("Audio/SE")?; - self.create_directory("Audio/ME")?; - - self.create_directory("Graphics")?; - self.create_directory("Graphics/Animations")?; - self.create_directory("Graphics/Autotiles")?; - self.create_directory("Graphics/Battlebacks")?; - self.create_directory("Graphics/Battlers")?; - self.create_directory("Graphics/Characters")?; - self.create_directory("Graphics/Fogs")?; - self.create_directory("Graphics/Icons")?; - self.create_directory("Graphics/Panoramas")?; - self.create_directory("Graphics/Pictures")?; - self.create_directory("Graphics/Tilesets")?; - self.create_directory("Graphics/Titles")?; - self.create_directory("Graphics/Transitions")?; - self.create_directory("Graphics/Windowskins")?; - - Ok(()) - } -} diff --git a/src/filesystem/archiver.rs b/src/filesystem/archiver.rs new file mode 100644 index 00000000..f13391c9 --- /dev/null +++ b/src/filesystem/archiver.rs @@ -0,0 +1,331 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::FileSystem as FileSystemTrait; +use super::{DirEntry, Error, Metadata, OpenFlags}; + +use itertools::Itertools; +use std::io::{prelude::*, Cursor, SeekFrom}; + +#[derive(Debug, Default)] +pub struct FileSystem { + files: dashmap::DashMap, + directories: dashmap::DashMap>, + archive_path: camino::Utf8PathBuf, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct Entry { + offset: u64, + size: u64, + start_magic: u32, +} + +const MAGIC: u32 = 0xDEADCAFE; +const HEADER: &[u8] = b"RGSSAD\0"; + +impl FileSystem { + pub fn new(project_path: impl AsRef) -> Result { + let project_path = project_path.as_ref(); + let archive_path = project_path + .read_dir_utf8()? + .flatten() + .map(camino::Utf8DirEntry::into_path) + .find(|entry| matches!(entry.extension(), Some("rgssad" | "rgss2a" | "rgss3a"))); + let Some(archive_path) = archive_path else { + return Err(Error::NotExist); + }; + + let mut file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(&archive_path)?; + let version = Self::read_header(&mut file)?; + + let files = dashmap::DashMap::new(); + let directories = dashmap::DashMap::new(); + + fn read_u32(file: &mut std::fs::File) -> Result { + let mut buffer = [0; 4]; + file.read_exact(&mut buffer)?; + Ok(u32::from_le_bytes(buffer)) + } + + fn read_u32_xor(file: &mut std::fs::File, key: u32) -> Result { + let result = read_u32(file)?; + Ok(result ^ key) + } + + match version { + 1 | 2 => { + let mut magic = MAGIC; + + while let Ok(name_len) = read_u32_xor(&mut file, Self::advance_magic(&mut magic)) { + let mut name = vec![0; name_len as usize]; + file.read_exact(&mut name).unwrap(); + for byte in name.iter_mut() { + let char = *byte ^ Self::advance_magic(&mut magic) as u8; + if char == b'\\' { + *byte = b'/'; + } else { + *byte = char; + } + } + let name = camino::Utf8PathBuf::from(String::from_utf8(name)?); + + Self::process_path(&directories, &name); + + let entry_len = read_u32_xor(&mut file, Self::advance_magic(&mut magic))?; + + let entry = Entry { + size: entry_len as u64, + offset: file.stream_position()?, + start_magic: magic, + }; + + files.insert(name, entry); + + file.seek(SeekFrom::Start(entry.offset + entry.size))?; + } + } + 3 => { + let mut u32_buf = [0; 4]; + file.read_exact(&mut u32_buf)?; + + let base_magic = u32::from_le_bytes(u32_buf); + let base_magic = (base_magic * 9) + 3; + + while let Ok(offset) = read_u32_xor(&mut file, base_magic) { + if offset == 0 { + break; + } + + let entry_len = read_u32_xor(&mut file, base_magic)?; + let magic = read_u32_xor(&mut file, base_magic)?; + let name_len = read_u32_xor(&mut file, base_magic)?; + + let mut name = vec![0; name_len as usize]; + file.read_exact(&mut name)?; + for (i, byte) in name.iter_mut().enumerate() { + let char = *byte ^ (base_magic >> (8 * (i % 4))) as u8; + if char == b'\\' { + *byte = b'/'; + } else { + *byte = char; + } + } + let name = camino::Utf8PathBuf::from(String::from_utf8(name)?); + + Self::process_path(&directories, &name); + + let entry = Entry { + size: entry_len as u64, + offset: offset as u64, + start_magic: magic, + }; + files.insert(name, entry); + } + } + _ => return Err(Error::InvalidHeader), + } + + /* + for dir in directories.iter() { + println!("==========="); + println!("{}", dir.key()); + for i in dir.value().iter() { + println!("{}", &*i); + } + println!("----------"); + } + */ + + Ok(FileSystem { + files, + directories, + archive_path, + }) + } + + fn process_path( + directories: &dashmap::DashMap>, + path: impl AsRef, + ) { + for (a, b) in path.as_ref().ancestors().tuple_windows() { + directories + .entry(b.to_path_buf()) + .or_default() + .insert(a.strip_prefix(b).unwrap_or(a).to_path_buf()); + } + } + + fn advance_magic(magic: &mut u32) -> u32 { + let old = *magic; + + *magic = magic.wrapping_mul(7).wrapping_add(3); + + old + } + + fn read_header(file: &mut std::fs::File) -> Result { + let mut header_buf = [0; 8]; + + file.read_exact(&mut header_buf)?; + + if !header_buf.starts_with(HEADER) { + return Err(Error::InvalidHeader); + } + + Ok(header_buf[7]) + } +} + +#[derive(Debug)] +pub struct File { + cursor: Cursor>, // TODO WRITE +} + +impl std::io::Write for File { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.cursor.write(buf) + } + + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { + self.cursor.write_vectored(bufs) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl std::io::Read for File { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.cursor.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { + self.cursor.read_vectored(bufs) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.cursor.read_exact(buf) + } +} + +impl std::io::Seek for File { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.cursor.seek(pos) + } + + fn stream_position(&mut self) -> std::io::Result { + self.cursor.stream_position() + } +} + +impl FileSystemTrait for FileSystem { + type File<'fs> = File where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + if flags.contains(OpenFlags::Write) { + return Err(Error::NotSupported); + } + + let entry = self.files.get(path.as_ref()).ok_or(Error::NotExist)?; + let mut buf = vec![0; entry.size as usize]; + + let mut archive = std::fs::File::open(&self.archive_path)?; + archive.seek(SeekFrom::Start(entry.offset))?; + archive.read_exact(&mut buf)?; + + let mut magic = entry.start_magic; + let mut j = 0; + for byte in buf.iter_mut() { + if j == 4 { + j = 0; + magic = magic.wrapping_mul(7).wrapping_add(3); + } + + *byte ^= bytemuck::cast::<_, [u8; 4]>(magic)[j]; + + j += 1; + } + + let cursor = Cursor::new(buf); + Ok(File { cursor }) + } + + fn metadata(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + if let Some(entry) = self.files.get(path) { + return Ok(Metadata { + is_file: true, + size: entry.size, + }); + } + + if let Some(directory) = self.directories.get(path) { + return Ok(Metadata { + is_file: false, + size: directory.len() as u64, + }); + } + + Err(Error::NotExist) + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> std::result::Result<(), Error> { + Err(Error::NotSupported) + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + Err(Error::NotSupported) + } + + fn exists(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + Ok(self.files.contains_key(path) || self.directories.contains_key(path)) + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + Err(Error::NotSupported) + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + Err(Error::NotSupported) + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + let path = path.as_ref(); + let directory = self.directories.get(path).ok_or(Error::NotExist)?; + directory + .iter() + .map(|entry| { + let path = path.join(&*entry); + let metadata = self.metadata(&path)?; + Ok(DirEntry { path, metadata }) + }) + .try_collect() + } +} diff --git a/src/filesystem/erased.rs b/src/filesystem/erased.rs new file mode 100644 index 00000000..db437934 --- /dev/null +++ b/src/filesystem/erased.rs @@ -0,0 +1,172 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::{DirEntry, Error, FileSystem, Metadata, OpenFlags}; + +pub trait File: std::io::Read + std::io::Write + std::io::Seek + Send + Sync {} +impl File for T where T: std::io::Read + std::io::Write + std::io::Seek + Send + Sync {} + +pub trait ErasedFilesystem: Send + Sync { + fn open_file( + &self, + path: &camino::Utf8Path, + flags: OpenFlags, + ) -> Result, Error>; + + fn metadata(&self, path: &camino::Utf8Path) -> Result; + + fn rename(&self, from: &camino::Utf8Path, to: &camino::Utf8Path) -> Result<(), Error>; + + fn exists(&self, path: &camino::Utf8Path) -> Result; + + fn create_dir(&self, path: &camino::Utf8Path) -> Result<(), Error>; + + fn remove_dir(&self, path: &camino::Utf8Path) -> Result<(), Error>; + + fn remove_file(&self, path: &camino::Utf8Path) -> Result<(), Error>; + + fn remove(&self, path: &camino::Utf8Path) -> Result<(), Error>; + + fn read_dir(&self, path: &camino::Utf8Path) -> Result, Error>; + + fn read(&self, path: &camino::Utf8Path) -> Result, Error>; + + fn read_to_string(&self, path: &camino::Utf8Path) -> Result; + + fn write(&self, path: &camino::Utf8Path, data: &[u8]) -> Result<(), Error>; +} + +impl ErasedFilesystem for T +where + T: FileSystem, +{ + fn open_file( + &self, + path: &camino::Utf8Path, + flags: OpenFlags, + ) -> Result, Error> { + let file = self.open_file(path, flags)?; + Ok(Box::new(file)) + } + + fn metadata(&self, path: &camino::Utf8Path) -> Result { + self.metadata(path) + } + + fn rename(&self, from: &camino::Utf8Path, to: &camino::Utf8Path) -> Result<(), Error> { + self.rename(from, to) + } + + fn exists(&self, path: &camino::Utf8Path) -> Result { + self.exists(path) + } + + fn create_dir(&self, path: &camino::Utf8Path) -> Result<(), Error> { + self.create_dir(path) + } + + fn remove_dir(&self, path: &camino::Utf8Path) -> Result<(), Error> { + self.remove_dir(path) + } + + fn remove_file(&self, path: &camino::Utf8Path) -> Result<(), Error> { + self.remove_file(path) + } + + fn remove(&self, path: &camino::Utf8Path) -> Result<(), Error> { + self.remove(path) + } + + fn read_dir(&self, path: &camino::Utf8Path) -> Result, Error> { + self.read_dir(path) + } + + fn read(&self, path: &camino::Utf8Path) -> Result, Error> { + self.read(path) + } + + fn read_to_string(&self, path: &camino::Utf8Path) -> Result { + self.read_to_string(path) + } + + fn write(&self, path: &camino::Utf8Path, data: &[u8]) -> Result<(), Error> { + self.write(path, data) + } +} + +impl FileSystem for dyn ErasedFilesystem { + type File<'fs> = Box where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + self.open_file(path.as_ref(), flags) + } + + fn metadata(&self, path: impl AsRef) -> Result { + self.metadata(path.as_ref()) + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> Result<(), Error> { + self.rename(from.as_ref(), to.as_ref()) + } + + fn exists(&self, path: impl AsRef) -> Result { + self.exists(path.as_ref()) + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + self.create_dir(path.as_ref()) + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + self.remove_dir(path.as_ref()) + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + self.remove_file(path.as_ref()) + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + self.read_dir(path.as_ref()) + } + + fn remove(&self, path: impl AsRef) -> Result<(), Error> { + self.remove(path.as_ref()) + } + + fn read(&self, path: impl AsRef) -> Result, Error> { + self.read(path.as_ref()) + } + + fn read_to_string(&self, path: impl AsRef) -> Result { + self.read_to_string(path.as_ref()) + } + + fn write( + &self, + path: impl AsRef, + data: impl AsRef<[u8]>, + ) -> Result<(), Error> { + self.write(path.as_ref(), data.as_ref()) + } +} diff --git a/src/filesystem/host.rs b/src/filesystem/host.rs new file mode 100644 index 00000000..52d06035 --- /dev/null +++ b/src/filesystem/host.rs @@ -0,0 +1,115 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::{DirEntry, Error, Metadata, OpenFlags}; +use itertools::Itertools; +use std::fs::File; + +#[derive(Debug, Clone)] +pub struct FileSystem { + root_path: camino::Utf8PathBuf, +} + +impl FileSystem { + pub fn new(root_path: impl AsRef) -> Self { + Self { + root_path: root_path.as_ref().to_path_buf(), + } + } + + pub fn root_path(&self) -> &camino::Utf8Path { + &self.root_path + } +} + +impl super::FileSystem for FileSystem { + type File<'fs> = File where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + let path = self.root_path.join(path); + std::fs::OpenOptions::new() + .create(flags.contains(OpenFlags::Create)) + .write(flags.contains(OpenFlags::Write)) + .read(flags.contains(OpenFlags::Read)) + .truncate(flags.contains(OpenFlags::Truncate)) + .open(path) + .map_err(Into::into) + } + + fn metadata(&self, path: impl AsRef) -> Result { + let path = self.root_path.join(path); + let metadata = std::fs::metadata(path)?; + Ok(Metadata { + is_file: metadata.is_file(), + size: metadata.len(), + }) + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> std::result::Result<(), Error> { + let from = self.root_path.join(from); + let to = self.root_path.join(to); + std::fs::rename(from, to).map_err(Into::into) + } + + fn exists(&self, path: impl AsRef) -> Result { + let path = self.root_path.join(path); + path.try_exists().map_err(Into::into) + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + let path = self.root_path.join(path); + std::fs::create_dir(path).map_err(Into::into) + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + let path = self.root_path.join(path); + std::fs::remove_dir_all(path).map_err(Into::into) + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + let path = self.root_path.join(path); + std::fs::remove_file(path).map_err(Into::into) + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + let path = self.root_path.join(path); + path.read_dir_utf8()? + .map_ok(|entry| { + let path = entry.into_path(); + let path = path + .strip_prefix(&self.root_path) + .unwrap_or(&path) + .to_path_buf(); + + // i hate windows. + #[cfg(windows)] + let path = path.into_string().replace('\\', "/").into(); + + let metadata = self.metadata(&path)?; + Ok(DirEntry::new(path, metadata)) + }) + .flatten() + .try_collect() + } +} diff --git a/src/filesystem/list.rs b/src/filesystem/list.rs new file mode 100644 index 00000000..20725c28 --- /dev/null +++ b/src/filesystem/list.rs @@ -0,0 +1,156 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::erased::{ErasedFilesystem, File}; +use super::FileSystem as FileSystemTrait; +use super::{DirEntry, Error, Metadata, OpenFlags}; +use itertools::Itertools; + +#[derive(Default)] +pub struct FileSystem { + filesystems: Vec>, +} + +impl FileSystem { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, fs: impl FileSystemTrait + 'static) { + self.filesystems.push(Box::new(fs)) + } +} + +impl FileSystemTrait for FileSystem { + type File<'fs> = Box where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + let path = path.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(path)? || flags.contains(OpenFlags::Create) { + return fs.open_file(path, flags); + } + } + Err(Error::NotExist) + } + + fn metadata(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(path)? { + return fs.metadata(path); + } + } + Err(Error::NotExist) + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> Result<(), Error> { + let from = from.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(from)? { + return fs.rename(from, to.as_ref()); + } + } + Err(Error::NotExist) + } + + fn exists(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(path)? { + return Ok(true); + } + } + Ok(false) + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + let fs = self.filesystems.first().ok_or(Error::NoFilesystems)?; + fs.create_dir(path.as_ref()) + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + let fs = self.filesystems.first().ok_or(Error::NoFilesystems)?; + fs.remove_dir(path.as_ref()) + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + let fs = self.filesystems.first().ok_or(Error::NoFilesystems)?; + fs.remove_file(path.as_ref()) + } + + fn remove(&self, path: impl AsRef) -> Result<(), Error> { + let fs = self.filesystems.first().ok_or(Error::NoFilesystems)?; + fs.remove(path.as_ref()) + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + let path = path.as_ref(); + + let mut entries = Vec::new(); + for fs in self.filesystems.iter() { + if fs.exists(path)? { + entries.extend(fs.read_dir(path)?) + } + } + // FIXME: remove duplicates in a more efficient manner + let entries = entries.into_iter().unique().collect_vec(); + + Ok(entries) + } + + fn read(&self, path: impl AsRef) -> Result, Error> { + let path = path.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(path)? { + return fs.read(path); + } + } + Err(Error::NotExist) + } + + fn read_to_string(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(path)? { + return fs.read_to_string(path); + } + } + Err(Error::NotExist) + } + + fn write( + &self, + path: impl AsRef, + data: impl AsRef<[u8]>, + ) -> Result<(), Error> { + let path = path.as_ref(); + for fs in self.filesystems.iter() { + if fs.exists(path)? { + return fs.write(path, data.as_ref()); + } + } + Err(Error::NotExist) + } +} diff --git a/src/filesystem/mod.rs b/src/filesystem/mod.rs new file mode 100644 index 00000000..7c45862d --- /dev/null +++ b/src/filesystem/mod.rs @@ -0,0 +1,163 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use std::io::prelude::*; + +mod erased; +mod host; +mod list; +mod path_cache; +pub mod project; + +mod archiver; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("File or directory does not exist")] + NotExist, + #[error("Io error {0}")] + IoError(#[from] std::io::Error), + #[error("UTF-8 Error {0}")] + Utf8Error(#[from] std::string::FromUtf8Error), + #[error("Project not loaded")] + NotLoaded, + #[error("Operation not supported by this filesystem")] + NotSupported, + #[error("Archive header is incorrect")] + InvalidHeader, + #[error("No filesystems are loaded to perform this operation")] + NoFilesystems, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct Metadata { + pub is_file: bool, + pub size: u64, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct DirEntry { + path: camino::Utf8PathBuf, + metadata: Metadata, +} + +impl DirEntry { + fn new(path: camino::Utf8PathBuf, metadata: Metadata) -> Self { + Self { path, metadata } + } + + pub fn path(&self) -> &camino::Utf8Path { + &self.path + } + + pub fn metadata(&self) -> Metadata { + self.metadata + } + + pub fn file_name(&self) -> &str { + self.path + .file_name() + .expect("path created through DirEntry must have a filename") + } + + pub fn into_path(self) -> camino::Utf8PathBuf { + self.path + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct OpenFlags: u8 { + const Read = 0b00000001; + const Write = 0b00000010; + const Truncate = 0b00000100; + const Create = 0b00001000; + } +} + +pub trait FileSystem: Send + Sync { + type File<'fs>: Read + Write + Seek + Send + Sync + 'fs + where + Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error>; + + fn metadata(&self, path: impl AsRef) -> Result; + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> Result<(), Error>; + + fn exists(&self, path: impl AsRef) -> Result; + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error>; + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error>; + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error>; + + fn remove(&self, path: impl AsRef) -> Result<(), Error> { + let path = path.as_ref(); + let metadata = self.metadata(path)?; + if metadata.is_file { + self.remove_file(path) + } else { + self.remove_dir(path) + } + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error>; + + /// Corresponds to [`std::fs::read()`]. + /// Will open a file at the path and read the entire file into a buffer. + fn read(&self, path: impl AsRef) -> Result, Error> { + let path = path.as_ref(); + + let mut buf = Vec::with_capacity(self.metadata(path)?.size as usize); + let mut file = self.open_file(path, OpenFlags::Read)?; + file.read_to_end(&mut buf)?; + + Ok(buf) + } + + fn read_to_string(&self, path: impl AsRef) -> Result { + let buf = self.read(path)?; + String::from_utf8(buf).map_err(Into::into) + } + + /// Corresponds to [`std::fs::write()`]. + /// Will open a file at the path, create it if it exists (and truncate it) and then write the provided bytes. + fn write( + &self, + path: impl AsRef, + data: impl AsRef<[u8]>, + ) -> Result<(), Error> { + let mut file = self.open_file( + path, + OpenFlags::Write | OpenFlags::Truncate | OpenFlags::Create, + )?; + file.write_all(data.as_ref())?; + file.flush()?; + + Ok(()) + } +} diff --git a/src/filesystem/overlay.rs b/src/filesystem/overlay.rs new file mode 100644 index 00000000..ea0933fb --- /dev/null +++ b/src/filesystem/overlay.rs @@ -0,0 +1,209 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::{DirEntry, Error, FileSystem, Metadata, OpenFlags}; +use itertools::Itertools; +use std::io::prelude::*; + +#[derive(Debug, Clone)] +pub struct Overlay { + primary: P, + secondary: S, +} + +impl Overlay { + pub fn new(primary: P, secondary: S) -> Self { + Self { primary, secondary } + } + + pub fn primary(&self) -> &P { + &self.primary + } + + pub fn secondary(&self) -> &S { + &self.secondary + } + + pub fn swap(self) -> Overlay { + Overlay { + secondary: self.primary, + primary: self.secondary, + } + } +} + +#[derive(Debug)] +pub enum File<'fs, P, S> +where + P: FileSystem + 'fs, + S: FileSystem + 'fs, +{ + Primary(P::File<'fs>), + Secondary(S::File<'fs>), +} + +impl<'fs, P, S> Write for File<'fs, P, S> +where + P: FileSystem, + S: FileSystem, +{ + fn write(&mut self, buf: &[u8]) -> std::io::Result { + match self { + Self::Primary(f) => f.write(buf), + Self::Secondary(f) => f.write(buf), + } + } + + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { + match self { + Self::Primary(f) => f.write_vectored(bufs), + Self::Secondary(f) => f.write_vectored(bufs), + } + } + + fn flush(&mut self) -> std::io::Result<()> { + match self { + Self::Primary(f) => f.flush(), + Self::Secondary(f) => f.flush(), + } + } +} + +impl<'fs, P, S> Read for File<'fs, P, S> +where + P: FileSystem, + S: FileSystem, +{ + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + match self { + Self::Primary(f) => f.read(buf), + Self::Secondary(f) => f.read(buf), + } + } + + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { + match self { + Self::Primary(f) => f.read_vectored(bufs), + Self::Secondary(f) => f.read_vectored(bufs), + } + } + + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + match self { + Self::Primary(f) => f.read_exact(buf), + Self::Secondary(f) => f.read_exact(buf), + } + } +} + +impl<'fs, P, S> Seek for File<'fs, P, S> +where + P: FileSystem, + S: FileSystem, +{ + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + match self { + Self::Primary(f) => f.seek(pos), + Self::Secondary(f) => f.seek(pos), + } + } + + fn stream_position(&mut self) -> std::io::Result { + match self { + Self::Primary(f) => f.stream_position(), + Self::Secondary(f) => f.stream_position(), + } + } +} + +impl FileSystem for Overlay +where + P: FileSystem, + S: FileSystem, +{ + type File<'fs> = File<'fs, P, S> where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + let path = path.as_ref(); + if self.primary.exists(path)? || flags.contains(OpenFlags::Create) { + return self.primary.open_file(path, flags).map(File::Primary); + } + + if self.secondary.exists(path)? { + return self.secondary.open_file(path, flags).map(File::Secondary); + } + + self.primary.open_file(path, flags).map(File::Primary) + } + + fn metadata(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + if self.primary.exists(path)? { + self.primary.metadata(path) + } else { + self.secondary.metadata(path) + } + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> Result<(), Error> { + self.primary.rename(from, to) + } + + fn exists(&self, path: impl AsRef) -> Result { + let path = path.as_ref(); + if self.primary.exists(path)? { + Ok(true) + } else { + self.secondary.exists(path) + } + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + self.primary.create_dir(path) + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + self.primary.remove_dir(path) + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + self.primary.remove_file(path) + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + let path = path.as_ref(); + + let mut entries = vec![]; // FIXME: inefficient + if self.primary.exists(path)? { + entries.extend(self.primary.read_dir(path)?); + } + if self.secondary.exists(path)? { + entries.extend(self.secondary.read_dir(path)?); + } + // FIXME: remove duplicates in a more efficient manner + let entries = entries.into_iter().unique().collect_vec(); + + Ok(entries) + } +} diff --git a/src/filesystem/path_cache.rs b/src/filesystem/path_cache.rs new file mode 100644 index 00000000..90009be9 --- /dev/null +++ b/src/filesystem/path_cache.rs @@ -0,0 +1,190 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::FileSystem as FileSystemTrait; +use super::{DirEntry, Error, Metadata, OpenFlags}; + +#[derive(Debug, Clone)] +pub struct FileSystem { + fs: F, + cache: dashmap::DashMap, +} + +impl FileSystem +where + F: FileSystemTrait, +{ + pub fn new(fs: F) -> Result { + let this = FileSystem { + fs, + cache: dashmap::DashMap::new(), + }; + this.regen_cache()?; + Ok(this) + } + + pub fn fs(&self) -> &F { + &self.fs + } + + pub fn regen_cache(&self) -> Result<(), Error> { + fn read_dir_recursive( + fs: &(impl FileSystemTrait + ?Sized), + path: impl AsRef, + mut f: impl FnMut(&camino::Utf8Path), + ) -> Result<(), Error> { + fn internal( + fs: &(impl FileSystemTrait + ?Sized), + path: impl AsRef, + f: &mut impl FnMut(&camino::Utf8Path), + ) -> Result<(), Error> { + for entry in fs.read_dir(path)? { + f(entry.path()); + if !entry.metadata().is_file { + internal(fs, entry.path(), f)?; + } + } + Ok(()) + } + internal(fs, path, &mut f) + } + + self.cache.clear(); + read_dir_recursive(&self.fs, "", |path| { + let mut lowercase = to_lowercase(path); + lowercase.set_extension(""); + + self.cache.insert(lowercase, path.to_path_buf()); + })?; + Ok(()) + } + + pub fn debug_ui(&self, ui: &mut egui::Ui) { + egui::ScrollArea::vertical() + .id_source("luminol_path_cache_debug_ui") + .show_rows( + ui, + ui.text_style_height(&egui::TextStyle::Body), + self.cache.len(), + |ui, rows| { + for (_, item) in self + .cache + .iter() + .enumerate() + .filter(|(index, _)| rows.contains(index)) + { + ui.horizontal(|ui| { + ui.label(item.key().as_str()); + ui.label("➡"); + ui.label(item.value().as_str()); + }); + } + }, + ); + } + + pub fn desensitize(&self, path: impl AsRef) -> Option { + let mut path = to_lowercase(path); + path.set_extension(""); + self.cache.get(&path).as_deref().cloned() + } +} + +pub fn to_lowercase(p: impl AsRef) -> camino::Utf8PathBuf { + p.as_ref().as_str().to_lowercase().into() +} + +impl FileSystemTrait for FileSystem +where + F: FileSystemTrait, +{ + type File<'fs> = F::File<'fs> where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + let path = path.as_ref(); + if flags.contains(OpenFlags::Create) { + let mut lower_path = to_lowercase(path); + lower_path.set_extension(""); + self.cache.insert(lower_path, path.to_path_buf()); + } + let path = self.desensitize(path).ok_or(Error::NotExist)?; + self.fs.open_file(path, flags) + } + + fn metadata(&self, path: impl AsRef) -> Result { + let path = self.desensitize(path).ok_or(Error::NotExist)?; + self.fs.metadata(path) + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> Result<(), Error> { + let from = self.desensitize(from).ok_or(Error::NotExist)?; + let to = to.as_ref().to_path_buf(); + + self.fs.rename(&from, &to)?; + + self.cache.remove(&from); + self.cache.insert(to_lowercase(&to), to); + + Ok(()) + } + + fn exists(&self, path: impl AsRef) -> Result { + Ok(self.desensitize(path).is_some()) + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + let path = path.as_ref().to_path_buf(); + + self.fs.create_dir(&path)?; + + self.cache.insert(to_lowercase(&path), path); + + Ok(()) + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + let path = self.desensitize(path).ok_or(Error::NotExist)?; + + self.fs.remove_dir(&path)?; + + self.cache.remove(&to_lowercase(path)); + + Ok(()) + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + let path = self.desensitize(path).ok_or(Error::NotExist)?; + + self.fs.remove_file(&path)?; + + self.cache.remove(&to_lowercase(path)); + + Ok(()) + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + let path = self.desensitize(path).ok_or(Error::NotExist)?; + self.fs.read_dir(path) + } +} diff --git a/src/filesystem/project.rs b/src/filesystem/project.rs new file mode 100644 index 00000000..7db46846 --- /dev/null +++ b/src/filesystem/project.rs @@ -0,0 +1,479 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +use super::FileSystem as FileSystemTrait; +use super::{archiver, host, list, path_cache, DirEntry, Error, Metadata, OpenFlags}; + +#[derive(Default)] +pub struct FileSystem { + state: AtomicRefCell, +} + +#[derive(Default)] +enum State { + #[default] + Unloaded, + HostLoaded(host::FileSystem), + Loaded { + filesystem: path_cache::FileSystem, + project_path: camino::Utf8PathBuf, + }, +} + +#[ouroboros::self_referencing] +pub struct File<'fs> { + state: AtomicRef<'fs, State>, + #[borrows(state)] + #[not_covariant] + file: FileType<'this>, +} + +enum FileType<'fs> { + Host(::File<'fs>), + Loaded( as FileSystemTrait>::File<'fs>), +} + +impl FileSystem { + pub fn new() -> Self { + Self::default() + } + + pub fn read_data(&self, path: impl AsRef) -> Result + where + T: serde::de::DeserializeOwned, + { + let data = self.read(path).map_err(|e| e.to_string())?; + + alox_48::from_bytes(&data).map_err(|e| e.to_string()) + } + + pub fn read_nil_padded(&self, path: impl AsRef) -> Result, String> + where + T: serde::de::DeserializeOwned, + { + let data = self.read(path).map_err(|e| e.to_string())?; + + alox_48::Deserializer::new(&data) + .and_then(|mut de| rmxp_types::nil_padded::deserialize(&mut de)) + .map_err(|e| e.to_string()) + } + + pub fn save_data(&self, path: impl AsRef, data: &T) -> Result<(), String> + where + T: serde::ser::Serialize, + { + self.write(path, alox_48::to_bytes(data).map_err(|e| e.to_string())?) + .map_err(|e| e.to_string()) + } + + pub fn save_nil_padded( + &self, + path: impl AsRef, + data: &[T], + ) -> Result<(), String> + where + T: serde::ser::Serialize, + { + let mut ser = alox_48::Serializer::new(); + rmxp_types::nil_padded::serialize(data, &mut ser).map_err(|e| e.to_string())?; + self.write(path, ser.output).map_err(|e| e.to_string()) + } + + pub fn project_path(&self) -> Option { + let state = self.state.borrow(); + match &*state { + State::Unloaded => None, + State::HostLoaded(h) => Some(h.root_path().to_path_buf()), + State::Loaded { project_path, .. } => Some(project_path.clone()), + } + } + + pub fn project_loaded(&self) -> bool { + !matches!(&*self.state.borrow(), State::Unloaded) + } + + pub fn unload_project(&self) { + *self.state.borrow_mut() = State::Unloaded; + } + + pub fn detect_rm_ver(&self) -> Option { + if self.exists("Data/Actors.rxdata").ok()? { + return Some(config::RMVer::XP); + } + + if self.exists("Data/Actors.rvdata").ok()? { + return Some(config::RMVer::VX); + } + + if self.exists("Data/Actors.rvdata2").ok()? { + return Some(config::RMVer::Ace); + } + + for path in self.read_dir("").ok()? { + let path = path.path(); + if path.extension() == Some("rgssad") { + return Some(config::RMVer::XP); + } + + if path.extension() == Some("rgss2a") { + return Some(config::RMVer::VX); + } + + if path.extension() == Some("rgss3a") { + return Some(config::RMVer::Ace); + } + } + + None + } + + pub async fn spawn_project_file_picker(&self) -> Result<(), String> { + if let Some(path) = rfd::AsyncFileDialog::default() + .add_filter("project file", &["rxproj", "rvproj", "rvproj2", "lumproj"]) + .pick_file() + .await + { + self.load_project(path.path()) + } else { + Err("Cancelled loading project".to_string()) + } + } + + #[cfg(windows)] + fn find_rtp_paths() -> Vec { + let ini = game_ini!(); + let Some(section) = ini.section(Some("Game")) else { + return vec![]; + }; + let mut paths = vec![]; + let mut seen_rtps = vec![]; + // FIXME: handle vx ace? + for rtp in ["RTP1", "RTP2", "RTP3"] { + if let Some(rtp) = section.get(rtp) { + if seen_rtps.contains(&rtp) || rtp.is_empty() { + continue; + } + seen_rtps.push(rtp); + + let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); + if let Ok(value) = hklm + .open_subkey("SOFTWARE\\WOW6432Node\\Enterbrain\\RGSS\\RTP") + .and_then(|key| key.get_value::(rtp)) + { + let path = camino::Utf8PathBuf::from(value); + if path.exists() { + paths.push(path); + continue; + } + } + + if let Ok(value) = hklm + .open_subkey("SOFTWARE\\WOW6432Node\\Enterbrain\\RPGXP") + .and_then(|key| key.get_value::("ApplicationPath")) + { + let path = camino::Utf8PathBuf::from(value).join("rtp"); + if path.exists() { + paths.push(path); + continue; + } + } + + if let Ok(value) = hklm + .open_subkey( + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 235900", + ) + .and_then(|key| key.get_value::("InstallLocation")) + { + let path = camino::Utf8PathBuf::from(value).join("rtp"); + if path.exists() { + paths.push(path); + continue; + } + } + + if let Some(path) = global_config!().rtp_paths.get(rtp) { + let path = camino::Utf8PathBuf::from(path); + if path.exists() { + paths.push(path); + continue; + } + } + + state!() + .toasts + .warning(format!("Failed to find suitable path for the RTP {rtp}")); + state!() + .toasts + .info(format!("You may want to set an RTP path for {rtp}")); + } + } + paths + } + + #[cfg(not(windows))] + fn find_rtp_paths() -> Vec { + let ini = game_ini!(); + let Some(section) = ini.section(Some("Game")) else { + return vec![]; + }; + let mut paths = vec![]; + let mut seen_rtps = vec![]; + // FIXME: handle vx ace? + for rtp in ["RTP1", "RTP2", "RTP3"] { + if let Some(rtp) = section.get(rtp) { + if seen_rtps.contains(&rtp) || rtp.is_empty() { + continue; + } + seen_rtps.push(rtp); + + if let Some(path) = global_config!().rtp_paths.get(rtp) { + let path = camino::Utf8PathBuf::from(path); + if path.exists() { + paths.push(path); + continue; + } + } + + state!() + .toasts + .warning(format!("Failed to find suitable path for the RTP {rtp}")); + state!() + .toasts + .info(format!("You may want to set an RTP path for {rtp}")); + } + } + paths + } + + pub fn load_project(&self, project_path: impl AsRef) -> Result<(), String> { + let original_path = camino::Utf8Path::from_path(project_path.as_ref()).unwrap(); + let path = original_path.parent().unwrap_or(original_path); + + *self.state.borrow_mut() = State::HostLoaded(host::FileSystem::new(path)); + + config::Project::load()?; + + let mut list = list::FileSystem::new(); + + list.push(host::FileSystem::new(path)); + + for path in Self::find_rtp_paths() { + list.push(host::FileSystem::new(path)) + } + + match archiver::FileSystem::new(path) { + Ok(a) => list.push(a), + Err(Error::NotExist) => (), + Err(e) => return Err(e.to_string()), + } + + let path_cache = path_cache::FileSystem::new(list).map_err(|e| e.to_string())?; + + *self.state.borrow_mut() = State::Loaded { + filesystem: path_cache, + project_path: path.to_path_buf(), + }; + + let mut projects: std::collections::VecDeque<_> = global_config!() + .recent_projects + .iter() + .filter(|p| p.as_str() != original_path) + .cloned() + .collect(); + projects.push_front(original_path.to_string()); + global_config!().recent_projects = projects; + + if let Err(e) = state!().data_cache.load() { + *self.state.borrow_mut() = State::Unloaded; + return Err(e); + } + + Ok(()) + } + + pub fn debug_ui(&self, ui: &mut egui::Ui) { + let state = self.state.borrow(); + match &*state { + State::Unloaded => { + ui.label("Unloaded"); + } + State::HostLoaded(fs) => { + ui.label("Host Filesystem Loaded"); + ui.horizontal(|ui| { + ui.label("Project path: "); + ui.label(fs.root_path().as_str()); + }); + } + State::Loaded { filesystem, .. } => { + ui.label("Loaded"); + filesystem.debug_ui(ui); + } + } + } +} + +impl<'fs> std::io::Write for File<'fs> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.write(buf), + FileType::Loaded(f) => f.write(buf), + }) + } + + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.write_vectored(bufs), + FileType::Loaded(f) => f.write_vectored(bufs), + }) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.flush(), + FileType::Loaded(f) => f.flush(), + }) + } +} + +impl<'fs> std::io::Read for File<'fs> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.read(buf), + FileType::Loaded(f) => f.read(buf), + }) + } + + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.read_vectored(bufs), + FileType::Loaded(f) => f.read_vectored(bufs), + }) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.read_exact(buf), + FileType::Loaded(f) => f.read_exact(buf), + }) + } +} + +impl<'fs> std::io::Seek for File<'fs> { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.seek(pos), + FileType::Loaded(f) => f.seek(pos), + }) + } + + fn stream_position(&mut self) -> std::io::Result { + self.with_file_mut(|f| match f { + FileType::Host(f) => f.stream_position(), + FileType::Loaded(f) => f.stream_position(), + }) + } +} + +impl FileSystemTrait for FileSystem { + type File<'fs> = File<'fs> where Self: 'fs; + + fn open_file( + &self, + path: impl AsRef, + flags: OpenFlags, + ) -> Result, Error> { + let state = self.state.borrow(); + File::try_new(state, |state| { + // + match &**state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.open_file(path, flags).map(FileType::Host), + State::Loaded { filesystem: f, .. } => { + f.open_file(path, flags).map(FileType::Loaded) + } + } + }) + } + + fn metadata(&self, path: impl AsRef) -> Result { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.metadata(path), + State::Loaded { filesystem: f, .. } => f.metadata(path), + } + } + + fn rename( + &self, + from: impl AsRef, + to: impl AsRef, + ) -> Result<(), Error> { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.rename(from, to), + State::Loaded { filesystem, .. } => filesystem.rename(from, to), + } + } + + fn exists(&self, path: impl AsRef) -> Result { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.exists(path), + State::Loaded { filesystem, .. } => filesystem.exists(path), + } + } + + fn create_dir(&self, path: impl AsRef) -> Result<(), Error> { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.create_dir(path), + State::Loaded { filesystem, .. } => filesystem.create_dir(path), + } + } + + fn remove_dir(&self, path: impl AsRef) -> Result<(), Error> { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.remove_dir(path), + State::Loaded { filesystem, .. } => filesystem.remove_dir(path), + } + } + + fn remove_file(&self, path: impl AsRef) -> Result<(), Error> { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.remove_file(path), + State::Loaded { filesystem, .. } => filesystem.remove_file(path), + } + } + + fn read_dir(&self, path: impl AsRef) -> Result, Error> { + let state = self.state.borrow(); + match &*state { + State::Unloaded => Err(Error::NotLoaded), + State::HostLoaded(f) => f.read_dir(path), + State::Loaded { filesystem, .. } => filesystem.read_dir(path), + } + } +} diff --git a/src/graphics/event.rs b/src/graphics/event.rs new file mode 100644 index 00000000..b402b9de --- /dev/null +++ b/src/graphics/event.rs @@ -0,0 +1,132 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use crate::prelude::*; + +#[derive(Debug)] +pub struct Event { + resources: Arc, + pub sprite_size: egui::Vec2, +} + +#[derive(Debug)] +struct Resources { + sprite: primitives::Sprite, + viewport: primitives::Viewport, +} + +type ResourcesSlab = slab::Slab>; + +impl Event { + // code smell, fix + pub fn new(event: &rpg::Event, atlas: &primitives::Atlas) -> Result, String> { + let Some(page) = event.pages.first() else { + return Err("event does not have first page".to_string()); + }; + + let texture = if let Some(ref filename) = page.graphic.character_name { + state!() + .image_cache + .load_wgpu_image("Graphics/Characters", filename)? + } else if page.graphic.tile_id.is_some() { + atlas.atlas_texture.clone() + } else { + return Ok(None); + }; + + let (quads, viewport, sprite_size) = if let Some(id) = page.graphic.tile_id { + // Why does this have to be + 1? + let quad = atlas.calc_quad((id + 1) as i16); + + let viewport = + primitives::Viewport::new(glam::Mat4::orthographic_rh(0.0, 32., 32., 0., -1., 1.)); + + (quad, viewport, egui::vec2(32., 32.)) + } else { + let cw = texture.width() as f32 / 4.; + let ch = texture.height() as f32 / 4.; + let pos = egui::Rect::from_min_size( + egui::pos2( + 0., //(event.x as f32 * 32.) + (16. - (cw / 2.)), + 0., //(event.y as f32 * 32.) + (32. - ch), + ), + egui::vec2(cw, ch), + ); + + // Reduced by 0.01 px on all sides to reduce texture bleeding + let tex_coords = egui::Rect::from_min_size( + egui::pos2( + page.graphic.pattern as f32 * cw + 0.01, + (page.graphic.direction as f32 - 2.) / 2. * ch + 0.01, + ), + egui::vec2(cw - 0.02, ch - 0.02), + ); + let quad = primitives::Quad::new(pos, tex_coords, 0.0); + + let viewport = + primitives::Viewport::new(glam::Mat4::orthographic_rh(0.0, cw, ch, 0., -1., 1.)); + + (quad, viewport, egui::vec2(cw, ch)) + }; + + let sprite = primitives::Sprite::new( + quads, + texture, + page.graphic.blend_type, + page.graphic.character_hue, + page.graphic.opacity, + ); + + Ok(Some(Self { + resources: Arc::new(Resources { sprite, viewport }), + sprite_size, + })) + } + + pub fn sprite(&self) -> &primitives::Sprite { + &self.resources.sprite + } + + pub fn paint(&self, painter: &egui::Painter, rect: egui::Rect) { + let resources = self.resources.clone(); + let resource_id = Arc::new(OnceCell::new()); + + let prepare_id = resource_id; + let paint_id = prepare_id.clone(); + let callback = egui_wgpu::CallbackFn::new() + .prepare(move |_device, _queue, _encoder, paint_callback_resources| { + let res_hash: &mut ResourcesSlab = paint_callback_resources + .entry() + .or_insert_with(Default::default); + let id = res_hash.insert(resources.clone()); + prepare_id.set(id).expect("resources id already set?"); + vec![] + }) + .paint(move |_info, render_pass, paint_callback_resources| { + let res_hash: &ResourcesSlab = paint_callback_resources.get().unwrap(); + let id = paint_id.get().copied().expect("resources id is unset"); + let resources = &res_hash[id]; + let Resources { viewport, sprite } = resources.as_ref(); + + sprite.draw(viewport, render_pass); + }); + painter.add(egui::PaintCallback { + callback: Arc::new(callback), + rect, + }); + } +} diff --git a/src/graphics/map.rs b/src/graphics/map.rs new file mode 100644 index 00000000..a1600c96 --- /dev/null +++ b/src/graphics/map.rs @@ -0,0 +1,172 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub use crate::prelude::*; + +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct Map { + resources: Arc, + ani_instant: Instant, + + pub fog_enabled: bool, + pub pano_enabled: bool, + pub enabled_layers: Vec, +} + +#[derive(Debug)] +struct Resources { + tiles: primitives::Tiles, + viewport: primitives::Viewport, + panorama: Option, + fog: Option, +} + +type ResourcesSlab = slab::Slab>; + +impl Map { + pub fn new(map: &rpg::Map, tileset: &rpg::Tileset) -> Result { + let atlas = state!().atlas_cache.load_atlas(tileset)?; + + let tiles = primitives::Tiles::new(atlas, &map.data); + + let panorama = if let Some(ref panorama_name) = tileset.panorama_name { + Some(Plane::new( + state!() + .image_cache + .load_wgpu_image("Graphics/Panoramas", panorama_name)?, + tileset.panorama_hue, + 100, + BlendMode::Normal, + 255, + map.width, + map.height, + )) + } else { + None + }; + let fog = if let Some(ref fog_name) = tileset.fog_name { + Some(Plane::new( + state!() + .image_cache + .load_wgpu_image("Graphics/Fogs", fog_name)?, + tileset.fog_hue, + tileset.fog_zoom, + tileset.fog_blend_type, + tileset.fog_opacity, + map.width, + map.height, + )) + } else { + None + }; + let viewport = primitives::Viewport::new(glam::Mat4::orthographic_rh( + 0.0, + map.width as f32 * 32., + map.height as f32 * 32., + 0.0, + -1.0, + 1.0, + )); + + Ok(Self { + resources: Arc::new(Resources { + tiles, + viewport, + panorama, + fog, + }), + + ani_instant: Instant::now(), + + fog_enabled: true, + pano_enabled: true, + enabled_layers: vec![true; map.data.zsize()], + }) + } + + pub fn set_tile(&self, tile_id: i16, position: (usize, usize, usize)) { + self.resources.tiles.set_tile(tile_id, position); + } + + pub fn paint( + &mut self, + painter: &egui::Painter, + selected_layer: Option, + rect: egui::Rect, + ) { + if self.ani_instant.elapsed() >= Duration::from_secs_f32((1. / 60.) * 16.) { + self.ani_instant = Instant::now(); + self.resources.tiles.autotiles.inc_ani_index(); + } + painter + .ctx() + .request_repaint_after(Duration::from_millis(16)); + + let resources = self.resources.clone(); + let resource_id = Arc::new(OnceCell::new()); + + let prepare_id = resource_id; + let paint_id = prepare_id.clone(); + + let fog_enabled = self.fog_enabled; + let pano_enabled = self.pano_enabled; + let enabled_layers = self.enabled_layers.clone(); + + let paint_callback = egui_wgpu::CallbackFn::new() + .prepare(move |_device, _queue, _encoder, paint_callback_resources| { + let res_hash: &mut ResourcesSlab = paint_callback_resources + .entry() + .or_insert_with(Default::default); + let id = res_hash.insert(resources.clone()); + prepare_id.set(id).expect("resources id already set?"); + + vec![] + }) + .paint(move |_info, render_pass, paint_callback_resources| { + let res_hash: &ResourcesSlab = paint_callback_resources.get().unwrap(); + let id = paint_id.get().copied().expect("resources id is unset"); + let resources = &res_hash[id]; + let Resources { + tiles, + viewport, + panorama, + fog, + .. + } = resources.as_ref(); + + if pano_enabled { + if let Some(panorama) = panorama { + panorama.draw(viewport, render_pass); + } + } + + tiles.draw(viewport, &enabled_layers, selected_layer, render_pass); + if fog_enabled { + if let Some(fog) = fog { + fog.draw(viewport, render_pass); + } + } + }); + + painter.add(egui::PaintCallback { + rect, + callback: Arc::new(paint_callback), + }); + } +} diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs new file mode 100644 index 00000000..412a8d34 --- /dev/null +++ b/src/graphics/mod.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +pub mod primitives; + +mod event; +mod map; +mod plane; + +pub use event::Event; +pub use map::Map; +pub use plane::Plane; diff --git a/src/graphics/plane.rs b/src/graphics/plane.rs new file mode 100644 index 00000000..14a0ee71 --- /dev/null +++ b/src/graphics/plane.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +#[derive(Debug)] +pub struct Plane { + sprite: primitives::Sprite, +} + +impl Plane { + pub fn new( + texture: Arc, + hue: i32, + zoom: i32, + blend_mode: BlendMode, + opacity: i32, + map_width: usize, + map_height: usize, + ) -> Self { + let zoom = zoom as f32 / 100.; + let map_width = map_width as f32 * 32.; + let map_height = map_height as f32 * 32.; + + let tex_coords = egui::Rect::from_min_size( + egui::pos2(0.0, 0.0), + egui::vec2(map_width / zoom, map_height / zoom), + ); + + let quad = primitives::Quad::new( + egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(map_width, map_height)), + tex_coords, + 0.0, + ); + + let sprite = primitives::Sprite::new(quad, texture, blend_mode, hue, opacity); + + Self { sprite } + } + + pub fn draw<'rpass>( + &'rpass self, + viewport: &primitives::Viewport, + render_pass: &mut wgpu::RenderPass<'rpass>, + ) { + self.sprite.draw(viewport, render_pass); + } +} diff --git a/src/project/mod.rs b/src/graphics/primitives/mod.rs similarity index 84% rename from src/project/mod.rs rename to src/graphics/primitives/mod.rs index d3fb0daa..5b4fff52 100644 --- a/src/project/mod.rs +++ b/src/graphics/primitives/mod.rs @@ -22,10 +22,16 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -/// The database of commands for this project. -mod command_db; -/// Luminol configuration -mod config; +mod quad; +mod sprite; +mod tiles; +mod vertex; +mod viewport; -pub use command_db::CommandDB; -pub use config::{LocalConfig, RGSSVer}; +pub use quad::Quad; +pub use vertex::Vertex; +pub use viewport::Viewport; + +pub use sprite::Sprite; +pub use tiles::Atlas; +pub use tiles::Tiles; diff --git a/src/graphics/primitives/quad.rs b/src/graphics/primitives/quad.rs new file mode 100644 index 00000000..7d0e0e3d --- /dev/null +++ b/src/graphics/primitives/quad.rs @@ -0,0 +1,125 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::vertex::Vertex; +use crate::prelude::*; +use wgpu::util::DeviceExt; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Quad { + pub pos: egui::Rect, + pub tex_coords: egui::Rect, + pub z: f32, +} + +impl Quad { + pub const fn new(pos: egui::Rect, tex_coords: egui::Rect, z: f32) -> Self { + Self { pos, tex_coords, z } + } + + fn norm_tex_coords(self, extents: wgpu::Extent3d) -> Self { + let scale = egui::vec2(extents.width as f32, extents.height as f32); + let min = self.tex_coords.min.to_vec2() / scale; + let max = self.tex_coords.max.to_vec2() / scale; + + Self { + tex_coords: egui::Rect::from_min_max(min.to_pos2(), max.to_pos2()), + ..self + } + } + + fn into_corners(self) -> [Vertex; 4] { + let Self { pos, tex_coords, z } = self; + let top_left = { + let position = pos.left_top(); + let tex_coords = tex_coords.left_top(); + Vertex { + position: glam::vec3(position.x, position.y, z), + tex_coords: glam::vec2(tex_coords.x, tex_coords.y), + } + }; + let top_right = { + let position = pos.right_top(); + let tex_coords = tex_coords.right_top(); + Vertex { + position: glam::vec3(position.x, position.y, z), + tex_coords: glam::vec2(tex_coords.x, tex_coords.y), + } + }; + let bottom_right = { + let position = pos.right_bottom(); + let tex_coords = tex_coords.right_bottom(); + Vertex { + position: glam::vec3(position.x, position.y, z), + tex_coords: glam::vec2(tex_coords.x, tex_coords.y), + } + }; + let bottom_left = { + let position = pos.left_bottom(); + let tex_coords = tex_coords.left_bottom(); + Vertex { + position: glam::vec3(position.x, position.y, z), + tex_coords: glam::vec2(tex_coords.x, tex_coords.y), + } + }; + [top_left, top_right, bottom_right, bottom_left] + } + + pub fn into_vertices(this: &[Self], extents: wgpu::Extent3d) -> Vec { + let mut vertices = Vec::with_capacity(this.len() * 6); + + // Quads are made like this: + // TL------TR + // | \ / | + // | / \ | + // BL-----BR + for quad in this { + let quad = quad.norm_tex_coords(extents); + let quad_verts = quad.into_corners(); + // Top left + vertices.push(quad_verts[0]); + // Top right + vertices.push(quad_verts[1]); + // Bottom left + vertices.push(quad_verts[3]); + + // Top right + vertices.push(quad_verts[1]); + // Bottom right + vertices.push(quad_verts[3]); + // Bottom left + vertices.push(quad_verts[2]); + } + + vertices + } + + pub fn into_buffer(this: &[Self], extents: wgpu::Extent3d) -> (wgpu::Buffer, usize) { + let render_state = &state!().render_state; + + let vertices = Self::into_vertices(this, extents); + + let buffer = render_state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("quad vertex buffer"), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + contents: bytemuck::cast_slice(&vertices), + }); + + (buffer, vertices.len()) + } +} diff --git a/src/graphics/primitives/sprite/graphic.rs b/src/graphics/primitives/sprite/graphic.rs new file mode 100644 index 00000000..3b899092 --- /dev/null +++ b/src/graphics/primitives/sprite/graphic.rs @@ -0,0 +1,86 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use crossbeam::atomic::AtomicCell; + +#[derive(Debug)] +pub struct Graphic { + data: AtomicCell, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Data { + hue: f32, + opacity: f32, + opacity_multiplier: f32, +} + +impl Graphic { + pub fn new(hue: i32, opacity: i32) -> Self { + let hue = (hue % 360) as f32 / 360.0; + let opacity = opacity as f32 / 255.; + let data = Data { + hue, + opacity, + opacity_multiplier: 1., + }; + + Self { + data: AtomicCell::new(data), + } + } + + pub fn hue(&self) -> i32 { + (self.data.load().hue * 360.) as i32 + } + + pub fn set_hue(&self, hue: i32) { + let hue = (hue % 360) as f32 / 360.0; + let data = self.data.load(); + + self.data.store(Data { hue, ..data }); + } + + pub fn opacity(&self) -> i32 { + (self.data.load().opacity * 255.) as i32 + } + + pub fn set_opacity(&self, opacity: i32) { + let opacity = opacity as f32 / 255.0; + let data = self.data.load(); + + self.data.store(Data { opacity, ..data }); + } + + pub fn opacity_multiplier(&self) -> f32 { + self.data.load().opacity_multiplier + } + + pub fn set_opacity_multiplier(&self, opacity_multiplier: f32) { + let data = self.data.load(); + + self.data.store(Data { + opacity_multiplier, + ..data + }); + } + + pub fn as_bytes(&self) -> [u8; std::mem::size_of::()] { + bytemuck::cast(self.data.load()) + } +} diff --git a/src/graphics/primitives/sprite/mod.rs b/src/graphics/primitives/sprite/mod.rs new file mode 100644 index 00000000..7f6bacc7 --- /dev/null +++ b/src/graphics/primitives/sprite/mod.rs @@ -0,0 +1,76 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +mod graphic; +mod shader; +mod vertices; + +use crate::prelude::*; +use primitives::Quad; +use primitives::Vertex; + +#[derive(Debug)] +pub struct Sprite { + pub texture: Arc, + pub graphic: graphic::Graphic, + pub vertices: vertices::Vertices, + pub blend_mode: BlendMode, +} + +impl Sprite { + pub fn new( + quad: Quad, + texture: Arc, + blend_mode: BlendMode, + hue: i32, + opacity: i32, + ) -> Self { + let vertices = vertices::Vertices::from_quads(&[quad], texture.size()); + let graphic = graphic::Graphic::new(hue, opacity); + + Self { + texture, + graphic, + vertices, + blend_mode, + } + } + + pub fn reupload_verts(&self, quads: &[Quad]) { + let render_state = &state!().render_state; + + let vertices = Quad::into_vertices(quads, self.texture.size()); + render_state.queue.write_buffer( + &self.vertices.vertex_buffer, + 0, + bytemuck::cast_slice(&vertices), + ); + } + + pub fn draw<'rpass>( + &'rpass self, + viewport: &primitives::Viewport, + render_pass: &mut wgpu::RenderPass<'rpass>, + ) { + shader::Shader::bind(self.blend_mode, render_pass); + render_pass.set_push_constants(wgpu::ShaderStages::VERTEX, 0, &viewport.as_bytes()); + render_pass.set_push_constants(wgpu::ShaderStages::FRAGMENT, 64, &self.graphic.as_bytes()); + + self.texture.bind(render_pass); + self.vertices.draw(render_pass); + } +} diff --git a/src/graphics/primitives/sprite/shader.rs b/src/graphics/primitives/sprite/shader.rs new file mode 100644 index 00000000..370c0d72 --- /dev/null +++ b/src/graphics/primitives/sprite/shader.rs @@ -0,0 +1,124 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +use super::{BlendMode, Vertex}; + +pub struct Shader { + pub pipeline: wgpu::RenderPipeline, +} + +impl Shader { + pub fn new(target: wgpu::BlendState) -> Self { + let render_state = &state!().render_state; + + let shader_module = render_state + .device + .create_shader_module(wgpu::include_wgsl!("sprite.wgsl")); + + let pipeline_layout = + render_state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Tilemap Sprite Pipeline Layout"), + bind_group_layouts: &[image_cache::Cache::bind_group_layout()], + push_constant_ranges: &[ + // Viewport + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX, + range: 0..64, + }, + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::FRAGMENT, + range: 64..(64 + 4 + 4 + 4), + }, + ], + }); + let pipeline = + render_state + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Tilemap Sprite Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader_module, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + blend: Some(target), + ..render_state.target_format.into() + })], + }), + primitive: wgpu::PrimitiveState { + // polygon_mode: wgpu::PolygonMode::Line, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + Shader { pipeline } + } + + pub fn bind(mode: BlendMode, render_pass: &mut wgpu::RenderPass<'_>) { + render_pass.set_pipeline(&EVENT_SHADERS[&mode].pipeline) + } +} + +static EVENT_SHADERS: Lazy> = Lazy::new(|| { + [ + (BlendMode::Normal, wgpu::BlendState::ALPHA_BLENDING), + ( + BlendMode::Add, + wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }, + ), + ( + BlendMode::Subtract, + wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::ReverseSubtract, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::Zero, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::ReverseSubtract, + }, + }, + ), + ] + .into_iter() + .map(|(mode, target)| (mode, Shader::new(target))) + .collect() +}); diff --git a/src/graphics/primitives/sprite/sprite.wgsl b/src/graphics/primitives/sprite/sprite.wgsl new file mode 100644 index 00000000..aac20d2d --- /dev/null +++ b/src/graphics/primitives/sprite/sprite.wgsl @@ -0,0 +1,85 @@ +// Vertex shader +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +} + +struct PushConstants { + viewport: Viewport, + graphic: Graphic, +} + +struct Viewport { + proj: mat4x4, +} + +struct Graphic { + hue: f32, + opacity: f32, + opacity_multiplier: f32, +} + +var push_constants: PushConstants; + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +fn rgb_to_hsv(c: vec3) -> vec3 { + let K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + let p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + let q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + let d = q.x - min(q.w, q.y); + + // Avoid divide - by - zero situations by adding a very tiny delta. + // Since we always deal with underlying 8 - Bit color values, this + // should never mask a real value + let eps = 1.0e-10; + + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + eps)), d / (q.x + eps), q.x); +} + +fn hsv_to_rgb(c: vec3) -> vec3 { + let K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + let p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + + return c.z * mix(K.xxx, clamp(p - K.xxx, vec3(0.0), vec3(1.0)), c.y); +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + + var position = push_constants.viewport.proj * vec4(model.position.xy, 0.0, 1.0); + + out.clip_position = vec4(position.xy, model.position.z, 1.0); + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var tex_sample = textureSample(t_diffuse, s_diffuse, in.tex_coords); + tex_sample.a *= push_constants.graphic.opacity * push_constants.graphic.opacity_multiplier; + if tex_sample.a <= 0. { + discard; + } + + if push_constants.graphic.hue > 0.0 { + var hsv = rgb_to_hsv(tex_sample.rgb); + + hsv.x += push_constants.graphic.hue; + tex_sample = vec4(hsv_to_rgb(hsv), tex_sample.a); + } + + return tex_sample; +} diff --git a/src/graphics/primitives/sprite/vertices.rs b/src/graphics/primitives/sprite/vertices.rs new file mode 100644 index 00000000..ad9a609e --- /dev/null +++ b/src/graphics/primitives/sprite/vertices.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use super::Quad; +use crate::prelude::*; + +#[derive(Debug)] +pub struct Vertices { + pub vertex_buffer: wgpu::Buffer, +} + +impl Vertices { + pub fn from_quads(quads: &[Quad], extents: wgpu::Extent3d) -> Self { + let (vertex_buffer, _) = Quad::into_buffer(quads, extents); + Self { vertex_buffer } + } + + pub fn draw<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.draw(0..6, 0..1) + } +} diff --git a/src/graphics/primitives/tiles/atlas.rs b/src/graphics/primitives/tiles/atlas.rs new file mode 100644 index 00000000..33df3061 --- /dev/null +++ b/src/graphics/primitives/tiles/atlas.rs @@ -0,0 +1,354 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use super::autotile_ids::AUTOTILES; +use super::primitives::Quad; + +pub const MAX_SIZE: u32 = 8192; // Max texture size in one dimension +pub const TILE_SIZE: u32 = 32; // Tiles are 32x32 +pub const TILESET_COLUMNS: u32 = 8; // Tilesets are 8 tiles across +pub const TILESET_WIDTH: u32 = TILE_SIZE * TILESET_COLUMNS; // self explanatory + +pub const AUTOTILE_ID_AMOUNT: u32 = 48; // there are 48 tile ids per autotile +pub const AUTOTILE_FRAME_COLS: u32 = TILESET_COLUMNS; // this is how many "columns" of autotiles there are per frame +pub const AUTOTILE_AMOUNT: u32 = 7; // There are 7 autotiles per tileset +pub const TOTAL_AUTOTILE_ID_AMOUNT: u32 = AUTOTILE_ID_AMOUNT * (AUTOTILE_AMOUNT + 1); // the first 384 tile ids are for autotiles (including empty tiles) + +pub const AUTOTILE_ROWS: u32 = AUTOTILE_ID_AMOUNT / AUTOTILE_FRAME_COLS; // split up the 48 tiles across each tileset row +pub const TOTAL_AUTOTILE_ROWS: u32 = AUTOTILE_ROWS * AUTOTILE_AMOUNT; // total number of rows for all autotiles combined +pub const AUTOTILE_ROW_HEIGHT: u32 = AUTOTILE_ROWS * TILE_SIZE; // This is how high one row of autotiles is +pub const TOTAL_AUTOTILE_HEIGHT: u32 = AUTOTILE_ROW_HEIGHT * AUTOTILE_AMOUNT; // self explanatory +pub const HEIGHT_UNDER_AUTOTILES: u32 = MAX_SIZE - TOTAL_AUTOTILE_HEIGHT; // this is the height under autotiles +pub const ROWS_UNDER_AUTOTILES: u32 = MAX_SIZE / TILE_SIZE - TOTAL_AUTOTILE_ROWS; // number of rows under autotiles +pub const ROWS_UNDER_AUTOTILES_TIMES_COLUMNS: u32 = ROWS_UNDER_AUTOTILES * TILESET_COLUMNS; + +pub const AUTOTILE_FRAME_WIDTH: u32 = AUTOTILE_FRAME_COLS * TILE_SIZE; // This is per frame! + +use crate::prelude::*; + +use image::GenericImageView; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct Atlas { + pub atlas_texture: Arc, + pub autotile_width: u32, + pub tileset_height: u32, + pub autotile_frames: [u32; AUTOTILE_AMOUNT as usize], +} + +impl Atlas { + pub fn new(tileset: &rpg::Tileset) -> Result { + let tileset_img = tileset.tileset_name.as_ref().and_then(|tileset_name| { + let tileset_img = state!() + .image_cache + .load_image("Graphics/Tilesets", tileset_name) + .ok()?; + Some(tileset_img.to_rgba8()) + }); + + let tileset_height = tileset_img + .as_ref() + .map(|i| i.height() / TILE_SIZE * TILE_SIZE) + .unwrap_or(256); + + let autotiles: Vec<_> = tileset + .autotile_names + .iter() + .map(|s| { + if s.is_empty() { + Ok(None) + } else { + state!() + .image_cache + .load_wgpu_image("Graphics/Autotiles", s) + .map(Some) + } + }) + .try_collect()?; + + let autotile_frames = std::array::from_fn(|i| { + autotiles[i] + .as_deref() + .map(image_cache::WgpuTexture::width) + // Why unwrap with a width of 96? Even though the autotile doesn't exist, it still has an effective width on the atlas of one frame. + // Further rendering code breaks down with an autotile width of 0, anyway. + .unwrap_or(96) + / 96 + }); + + let autotile_width = autotile_frames + .iter() + .map(|f| f * AUTOTILE_FRAME_WIDTH) + .max() + .unwrap_or(AUTOTILE_FRAME_WIDTH); + + let render_state = &state!().render_state; + let mut encoder = + render_state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("atlas creation"), + }); + + let width; + let height; + + let rows_under; + let rows_side; + if TOTAL_AUTOTILE_HEIGHT + tileset_height < MAX_SIZE { + width = autotile_width.max(TILESET_WIDTH); // in case we have less autotiles frames than the tileset is wide + height = TOTAL_AUTOTILE_HEIGHT + tileset_height; // we're sure that the tileset can fit into the atlas just fine + + rows_under = 1; + rows_side = 0; + } else { + // Find out how many rows are under autotiles + // Take the smallest of these + rows_under = u32::min( + // How many times can the tileset fit under the autotiles? + tileset_height.div_ceil(HEIGHT_UNDER_AUTOTILES), + // How many columns of autotiles are there + autotile_width.div_ceil(TILESET_WIDTH), + ); + // Find out how many rows would fit on the side by dividing the left over height by MAX_SIZE + rows_side = tileset_height + .saturating_sub(rows_under * HEIGHT_UNDER_AUTOTILES) + .div_ceil(MAX_SIZE); + + width = ((rows_under + rows_side) * TILESET_WIDTH).max(autotile_width); + height = MAX_SIZE; + } + + let atlas_texture = render_state + .device + .create_texture(&wgpu::TextureDescriptor { + label: Some("tileset_atlas"), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + dimension: wgpu::TextureDimension::D2, + mip_level_count: 1, + sample_count: 1, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + let mut atlas_copy = atlas_texture.as_image_copy(); + + for (index, autotile_texture) in + autotiles + .into_iter() + .enumerate() + .flat_map(|(index, autotile_texture)| { + autotile_texture.map(|autotile_texture| (index, autotile_texture)) + }) + { + let mut autotile_copy = autotile_texture.texture.as_image_copy(); + + let frame_y = index as u32 * AUTOTILE_ROW_HEIGHT; + for frame in 0..autotile_frames[index] { + let frame_x = frame * AUTOTILE_FRAME_WIDTH; + for (index, autotile) in AUTOTILES.into_iter().enumerate() { + // Reset x every 8 tiles + let autotile_x = index as u32 % AUTOTILE_FRAME_COLS * TILE_SIZE; + // Increase y every 8 tiles + let autotile_y = index as u32 / AUTOTILE_FRAME_COLS * TILE_SIZE; + + for (index, sub_tile) in autotile.into_iter().enumerate() { + let sub_tile_x = index as u32 % 2 * 16; + let sub_tile_y = index as u32 / 2 * 16; + + atlas_copy.origin.x = frame_x + autotile_x + sub_tile_x; + atlas_copy.origin.y = frame_y + autotile_y + sub_tile_y; + + let tile_x = sub_tile % 6 * 16; + let tile_y = sub_tile / 6 * 16; + + autotile_copy.origin.x = tile_x + frame * 96; + autotile_copy.origin.y = tile_y; + + encoder.copy_texture_to_texture( + autotile_copy, + atlas_copy, + wgpu::Extent3d { + width: 16, + height: 16, + depth_or_array_layers: 1, + }, + ); + } + } + } + } + + render_state.queue.submit(std::iter::once(encoder.finish())); + + atlas_copy.origin.x = 0; + if let Some(tileset_img) = tileset_img { + if TOTAL_AUTOTILE_HEIGHT + tileset_height < MAX_SIZE { + write_texture_region( + &atlas_texture, + tileset_img.view(0, 0, TILESET_WIDTH, tileset_height), + (0, TOTAL_AUTOTILE_HEIGHT), + ) + } else { + for i in 0..rows_under { + let y = HEIGHT_UNDER_AUTOTILES * i; + let height = if y + HEIGHT_UNDER_AUTOTILES > tileset_height { + tileset_height - y + } else { + HEIGHT_UNDER_AUTOTILES + }; + write_texture_region( + &atlas_texture, + tileset_img.view(0, y, TILESET_WIDTH, height), + (TILESET_WIDTH * i, TOTAL_AUTOTILE_HEIGHT), + ) + } + for i in 0..rows_side { + let y = (HEIGHT_UNDER_AUTOTILES * rows_under) + MAX_SIZE * i; + let height = if y + MAX_SIZE > tileset_height { + tileset_height - y + } else { + MAX_SIZE + }; + write_texture_region( + &atlas_texture, + tileset_img.view(0, y, TILESET_WIDTH, height), + (TILESET_WIDTH * (rows_under + i), 0), + ) + } + } + } + + let bind_group = image_cache::Cache::create_texture_bind_group(&atlas_texture); + let atlas_texture = Arc::new(image_cache::WgpuTexture::new(atlas_texture, bind_group)); + + Ok(Atlas { + atlas_texture, + autotile_width, + + tileset_height, + autotile_frames, + }) + } + + pub fn calc_quad(&self, tile: i16) -> Quad { + let tile_u32 = if tile < 0 { 0 } else { tile as u32 }; + + let is_autotile = tile_u32 < TOTAL_AUTOTILE_ID_AMOUNT; + let max_frame_count = self.autotile_width / AUTOTILE_FRAME_WIDTH; + let max_tiles_under_autotiles = max_frame_count * ROWS_UNDER_AUTOTILES_TIMES_COLUMNS; + let is_under_autotiles = + !is_autotile && tile_u32 - TOTAL_AUTOTILE_ID_AMOUNT < max_tiles_under_autotiles; + + let atlas_tile_position = if tile_u32 < AUTOTILE_ID_AMOUNT { + egui::pos2(0., 0.) + } else if is_autotile { + egui::pos2( + ((tile_u32 - AUTOTILE_ID_AMOUNT) % AUTOTILE_FRAME_COLS * TILE_SIZE) as f32, + ((tile_u32 - AUTOTILE_ID_AMOUNT) / AUTOTILE_FRAME_COLS * TILE_SIZE) as f32, + ) + } else if is_under_autotiles { + egui::pos2( + ((tile_u32 % TILESET_COLUMNS + + (tile_u32 - TOTAL_AUTOTILE_ID_AMOUNT) / ROWS_UNDER_AUTOTILES_TIMES_COLUMNS + * TILESET_COLUMNS) + * TILE_SIZE) as f32, + (((tile_u32 - TOTAL_AUTOTILE_ID_AMOUNT) / TILESET_COLUMNS % ROWS_UNDER_AUTOTILES + + TOTAL_AUTOTILE_ROWS) + * TILE_SIZE) as f32, + ) + } else { + egui::pos2( + ((tile_u32 % TILESET_COLUMNS + + ((tile_u32 - TOTAL_AUTOTILE_ID_AMOUNT - max_tiles_under_autotiles) + / (MAX_SIZE / TILE_SIZE * TILESET_COLUMNS) + + max_frame_count) + * TILESET_COLUMNS) + * TILE_SIZE) as f32, + ((tile_u32 - TOTAL_AUTOTILE_ID_AMOUNT - max_tiles_under_autotiles) + / TILESET_COLUMNS + % (MAX_SIZE / TILE_SIZE) + * TILE_SIZE) as f32, + ) + }; + + Quad::new( + egui::Rect::from_min_size( + egui::pos2(0., 0.), + egui::vec2(TILE_SIZE as f32, TILE_SIZE as f32), + ), + // Reduced by 0.01 px on all sides to decrease texture bleeding + egui::Rect::from_min_size( + atlas_tile_position + egui::vec2(0.01, 0.01), + egui::vec2(TILE_SIZE as f32 - 0.02, TILE_SIZE as f32 - 0.02), + ), + 0.0, + ) + } + + pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { + self.atlas_texture.bind(render_pass); + } +} + +fn write_texture_region

( + texture: &wgpu::Texture, + image: image::SubImage<&image::ImageBuffer>>, + (dest_x, dest_y): (u32, u32), +) where + P: image::Pixel, + P::Subpixel: bytemuck::Pod, +{ + let (x, y, width, height) = image.bounds(); + let bytes = bytemuck::cast_slice(image.inner().as_raw()); + + let inner_width = image.inner().width(); + // let inner_width = subimage.width(); + let stride = inner_width * std::mem::size_of::

() as u32; + let offset = (y * inner_width + x) * std::mem::size_of::

() as u32; + + state!().render_state.queue.write_texture( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: dest_x, + y: dest_y, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + bytes, + wgpu::ImageDataLayout { + offset: offset as wgpu::BufferAddress, + bytes_per_row: Some(stride), + rows_per_image: None, + }, + wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); +} diff --git a/src/graphics/primitives/tiles/autotile_ids.rs b/src/graphics/primitives/tiles/autotile_ids.rs new file mode 100644 index 00000000..000e6f54 --- /dev/null +++ b/src/graphics/primitives/tiles/autotile_ids.rs @@ -0,0 +1,369 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +/* +#[derive(Clone, Copy, Debug, Default)] +pub struct Autotile { + pub x: f32, + pub y: f32, +} + +pub const AUTOTILES: [[Autotile; 4]; 48] = [ + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 32.5, y: 80.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 48.5, y: 80.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 48.5, y: 64.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 32.5, y: 64.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 0.5, y: 64.5 }, + Autotile { x: 16.5, y: 64.5 }, + Autotile { x: 0.5, y: 80.5 }, + Autotile { x: 16.5, y: 80.5 }, + ], + [ + Autotile { x: 0.5, y: 64.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 0.5, y: 80.5 }, + Autotile { x: 16.5, y: 80.5 }, + ], + [ + Autotile { x: 0.5, y: 64.5 }, + Autotile { x: 16.5, y: 64.5 }, + Autotile { x: 0.5, y: 80.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 0.5, y: 64.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 0.5, y: 80.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 32.5, y: 32.5 }, + Autotile { x: 48.5, y: 32.5 }, + Autotile { x: 32.5, y: 48.5 }, + Autotile { x: 48.5, y: 48.5 }, + ], + [ + Autotile { x: 32.5, y: 32.5 }, + Autotile { x: 48.5, y: 32.5 }, + Autotile { x: 32.5, y: 48.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 32.5, y: 32.5 }, + Autotile { x: 48.5, y: 32.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 48.5, y: 48.5 }, + ], + [ + Autotile { x: 32.5, y: 32.5 }, + Autotile { x: 48.5, y: 32.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 64.5, y: 64.5 }, + Autotile { x: 80.5, y: 64.5 }, + Autotile { x: 64.5, y: 80.5 }, + Autotile { x: 80.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 64.5 }, + Autotile { x: 80.5, y: 64.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 64.5 }, + Autotile { x: 64.5, y: 80.5 }, + Autotile { x: 80.5, y: 80.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 64.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 80.5 }, + ], + [ + Autotile { x: 32.5, y: 96.5 }, + Autotile { x: 48.5, y: 96.5 }, + Autotile { x: 32.5, y: 112.5 }, + Autotile { x: 48.5, y: 112.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 48.5, y: 96.5 }, + Autotile { x: 32.5, y: 112.5 }, + Autotile { x: 48.5, y: 112.5 }, + ], + [ + Autotile { x: 32.5, y: 96.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 32.5, y: 112.5 }, + Autotile { x: 48.5, y: 112.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 32.5, y: 112.5 }, + Autotile { x: 48.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 64.5 }, + Autotile { x: 80.5, y: 64.5 }, + Autotile { x: 0.5, y: 80.5 }, + Autotile { x: 80.5, y: 80.5 }, + ], + [ + Autotile { x: 32.5, y: 32.5 }, + Autotile { x: 48.5, y: 32.5 }, + Autotile { x: 32.5, y: 112.5 }, + Autotile { x: 48.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 32.5 }, + Autotile { x: 16.5, y: 32.5 }, + Autotile { x: 0.5, y: 48.5 }, + Autotile { x: 16.5, y: 48.5 }, + ], + [ + Autotile { x: 0.5, y: 32.5 }, + Autotile { x: 16.5, y: 32.5 }, + Autotile { x: 0.5, y: 48.5 }, + Autotile { x: 80.5, y: 16.5 }, + ], + [ + Autotile { x: 64.5, y: 32.5 }, + Autotile { x: 80.5, y: 32.5 }, + Autotile { x: 64.5, y: 48.5 }, + Autotile { x: 80.5, y: 48.5 }, + ], + [ + Autotile { x: 64.5, y: 32.5 }, + Autotile { x: 80.5, y: 32.5 }, + Autotile { x: 64.5, y: 16.5 }, + Autotile { x: 80.5, y: 48.5 }, + ], + [ + Autotile { x: 64.5, y: 96.5 }, + Autotile { x: 80.5, y: 96.5 }, + Autotile { x: 64.5, y: 112.5 }, + Autotile { x: 80.5, y: 112.5 }, + ], + [ + Autotile { x: 64.5, y: 0.5 }, + Autotile { x: 80.5, y: 96.5 }, + Autotile { x: 64.5, y: 112.5 }, + Autotile { x: 80.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 96.5 }, + Autotile { x: 16.5, y: 96.5 }, + Autotile { x: 0.5, y: 112.5 }, + Autotile { x: 16.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 96.5 }, + Autotile { x: 80.5, y: 0.5 }, + Autotile { x: 0.5, y: 112.5 }, + Autotile { x: 16.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 32.5 }, + Autotile { x: 80.5, y: 32.5 }, + Autotile { x: 0.5, y: 48.5 }, + Autotile { x: 80.5, y: 48.5 }, + ], + [ + Autotile { x: 0.5, y: 32.5 }, + Autotile { x: 16.5, y: 32.5 }, + Autotile { x: 0.5, y: 112.5 }, + Autotile { x: 16.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 96.5 }, + Autotile { x: 80.5, y: 96.5 }, + Autotile { x: 0.5, y: 112.5 }, + Autotile { x: 80.5, y: 112.5 }, + ], + [ + Autotile { x: 64.5, y: 32.5 }, + Autotile { x: 80.5, y: 32.5 }, + Autotile { x: 64.5, y: 112.5 }, + Autotile { x: 80.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 32.5 }, + Autotile { x: 80.5, y: 32.5 }, + Autotile { x: 0.5, y: 112.5 }, + Autotile { x: 80.5, y: 112.5 }, + ], + [ + Autotile { x: 0.5, y: 0.5 }, + Autotile { x: 16.5, y: 0.5 }, + Autotile { x: 0.5, y: 16.5 }, + Autotile { x: 16.5, y: 16.5 }, + ], +]; +*/ + +/// Hardcoded list of tiles from r48 and old python Luminol. +/// There seems to be very little pattern in autotile IDs so this is sadly +/// the best we can do. +pub const AUTOTILES: [[u32; 4]; 48] = [ + [26, 27, 32, 33], + [4, 27, 32, 33], + [26, 5, 32, 33], + [4, 5, 32, 33], + [26, 27, 32, 11], + [4, 27, 32, 11], + [26, 5, 32, 11], + [4, 5, 32, 11], + [26, 27, 10, 33], + [4, 27, 10, 33], + [26, 5, 10, 33], + [4, 5, 10, 33], + [26, 27, 10, 11], + [4, 27, 10, 11], + [26, 5, 10, 11], + [4, 5, 10, 11], + [24, 25, 30, 31], + [24, 5, 30, 31], + [24, 25, 30, 11], + [24, 5, 30, 11], + [14, 15, 20, 21], + [14, 15, 20, 11], + [14, 15, 10, 21], + [14, 15, 10, 11], + [28, 29, 34, 35], + [28, 29, 10, 35], + [4, 29, 34, 35], + [4, 29, 10, 35], + [38, 39, 44, 45], + [4, 39, 44, 45], + [38, 5, 44, 45], + [4, 5, 44, 45], + [24, 29, 30, 35], + [14, 15, 44, 45], + [12, 13, 18, 19], + [12, 13, 18, 11], + [16, 17, 22, 23], + [16, 17, 10, 23], + [40, 41, 46, 47], + [4, 41, 46, 47], + [36, 37, 42, 43], + [36, 5, 42, 43], + [12, 17, 18, 23], + [12, 13, 42, 43], + [36, 41, 42, 47], + [16, 17, 46, 47], + [12, 17, 42, 47], + [0, 1, 6, 7], +]; diff --git a/src/graphics/primitives/tiles/autotiles.rs b/src/graphics/primitives/tiles/autotiles.rs new file mode 100644 index 00000000..1da79211 --- /dev/null +++ b/src/graphics/primitives/tiles/autotiles.rs @@ -0,0 +1,57 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use crossbeam::atomic::AtomicCell; + +#[derive(Debug)] +pub struct Autotiles { + data: AtomicCell, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +struct Data { + ani_index: u32, + autotile_region_width: u32, + autotile_frames: [u32; 7], +} + +impl Autotiles { + pub fn new(atlas: &super::Atlas) -> Self { + let autotiles = Data { + autotile_frames: atlas.autotile_frames, + autotile_region_width: atlas.autotile_width, + ani_index: 0, + }; + + Autotiles { + data: AtomicCell::new(autotiles), + } + } + + pub fn inc_ani_index(&self) { + let data = self.data.load(); + self.data.store(Data { + ani_index: data.ani_index.wrapping_add(1), + ..data + }); + } + + pub fn as_bytes(&self) -> [u8; std::mem::size_of::()] { + bytemuck::cast(self.data.load()) + } +} diff --git a/src/graphics/primitives/tiles/instance.rs b/src/graphics/primitives/tiles/instance.rs new file mode 100644 index 00000000..cfe72400 --- /dev/null +++ b/src/graphics/primitives/tiles/instance.rs @@ -0,0 +1,138 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; +use primitives::Quad; + +use wgpu::util::DeviceExt; + +#[derive(Debug)] +pub struct Instances { + instance_buffer: wgpu::Buffer, + vertex_buffer: wgpu::Buffer, + + map_width: usize, + map_height: usize, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +struct Instance { + position: [f32; 3], + tile_id: i32, // force this to be an i32 to avoid padding issues +} + +const TILE_QUAD: Quad = Quad::new( + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(32., 32.0)), + // slightly smaller than 32x32 to reduce bleeding from adjacent pixels in the atlas + egui::Rect::from_min_max(egui::pos2(0.01, 0.01), egui::pos2(31.99, 31.99)), + 0.0, +); + +impl Instances { + pub fn new(map_data: &Table3, atlas_size: wgpu::Extent3d) -> Self { + let instances = Self::calculate_instances(map_data); + let instance_buffer = + state!() + .render_state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("tilemap tiles instance buffer"), + contents: bytemuck::cast_slice(&instances), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + + let (vertex_buffer, _) = Quad::into_buffer(&[TILE_QUAD], atlas_size); + + Self { + instance_buffer, + vertex_buffer, + + map_width: map_data.xsize(), + map_height: map_data.ysize(), + } + } + + // I thought we didn't need the z? Well.. we do! To calculate the offset into the instance buffer. + pub fn set_tile(&self, tile_id: i16, position: (usize, usize, usize)) { + let offset = position.0 + + (position.1 * self.map_width) + + (position.2 * self.map_width * self.map_height); + let offset = offset * std::mem::size_of::(); + state!().render_state.queue.write_buffer( + &self.instance_buffer, + offset as wgpu::BufferAddress, + bytemuck::bytes_of(&Instance { + position: [position.0 as f32, position.1 as f32, 0.0], + tile_id: tile_id as i32, + }), + ) + } + + fn calculate_instances(map_data: &Table3) -> Vec { + map_data + .iter() + .copied() + .enumerate() + // Previously we'd filter out tiles that would not display (anything < 48). + // However, storing the entire map like this makes it easier to edit tiles without remaking the entire buffer. + // It's a memory tradeoff for a lot of performance. + .map(|(index, tile_id)| { + // We reset the x every xsize elements. + let map_x = index % map_data.xsize(); + // We reset the y every ysize elements, but only increment it every xsize elements. + let map_y = (index / map_data.xsize()) % map_data.ysize(); + // We don't need the z. + + Instance { + position: [ + map_x as f32, + map_y as f32, + 0., // We don't do a depth buffer. z doesn't matter + ], + tile_id: tile_id as i32, + } + }) + .collect_vec() + } + + pub fn draw<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>, layer: usize) { + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + + // Calculate the start and end index of the buffer, as well as the amount of instances. + let start_index = layer * self.map_width * self.map_height; + let end_index = (layer + 1) * self.map_width * self.map_height; + let count = (end_index - start_index) as u32; + + // Convert the indexes into actual offsets. + let start = (start_index * std::mem::size_of::()) as wgpu::BufferAddress; + let end = (end_index * std::mem::size_of::()) as wgpu::BufferAddress; + + render_pass.set_vertex_buffer(1, self.instance_buffer.slice(start..end)); + + render_pass.draw(0..6, 0..count); + } + + pub const fn desc() -> wgpu::VertexBufferLayout<'static> { + const ARRAY: &[wgpu::VertexAttribute] = + &wgpu::vertex_attr_array![2 => Float32x3, 3 => Sint32]; + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: ARRAY, + } + } +} diff --git a/src/graphics/primitives/tiles/mod.rs b/src/graphics/primitives/tiles/mod.rs new file mode 100644 index 00000000..ec2f41f9 --- /dev/null +++ b/src/graphics/primitives/tiles/mod.rs @@ -0,0 +1,98 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +pub use atlas::Atlas; + +use autotiles::Autotiles; +use instance::Instances; +use shader::Shader; + +mod atlas; +mod autotile_ids; +mod autotiles; +mod instance; +mod shader; + +#[derive(Debug)] +pub struct Tiles { + pub autotiles: Autotiles, + pub atlas: Atlas, + pub instances: Instances, +} + +impl Tiles { + pub fn new(atlas: Atlas, tiles: &Table3) -> Self { + let autotiles = Autotiles::new(&atlas); + let instances = Instances::new(tiles, atlas.atlas_texture.size()); + + Self { + autotiles, + atlas, + instances, + } + } + + pub fn set_tile(&self, tile_id: i16, position: (usize, usize, usize)) { + self.instances.set_tile(tile_id, position) + } + + pub fn draw<'rpass>( + &'rpass self, + viewport: &primitives::Viewport, + enabled_layers: &[bool], + selected_layer: Option, + render_pass: &mut wgpu::RenderPass<'rpass>, + ) { + #[repr(C)] + #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] + struct VertexPushConstant { + viewport: [u8; 64], + autotiles: [u8; 36], + } + + render_pass.push_debug_group("tilemap tiles renderer"); + Shader::bind(render_pass); + render_pass.set_push_constants( + wgpu::ShaderStages::VERTEX, + 0, + bytemuck::bytes_of(&VertexPushConstant { + viewport: viewport.as_bytes(), + autotiles: self.autotiles.as_bytes(), + }), + ); + + self.atlas.bind(render_pass); + + for (layer, enabled) in enabled_layers.iter().copied().enumerate() { + let opacity = match selected_layer { + Some(selected_layer) if selected_layer == layer => 1.0, + Some(_) => 0.5, + None => 1.0, + }; + render_pass.set_push_constants( + wgpu::ShaderStages::FRAGMENT, + 64 + 36, + bytemuck::bytes_of::(&opacity), + ); + if enabled { + self.instances.draw(render_pass, layer); + } + } + render_pass.pop_debug_group(); + } +} diff --git a/src/graphics/primitives/tiles/shader.rs b/src/graphics/primitives/tiles/shader.rs new file mode 100644 index 00000000..6e68d88b --- /dev/null +++ b/src/graphics/primitives/tiles/shader.rs @@ -0,0 +1,87 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +use crate::prelude::*; + +use super::instance::Instances; +use primitives::Vertex; + +#[derive(Debug)] +pub struct Shader { + pub pipeline: wgpu::RenderPipeline, +} + +impl Shader { + fn new() -> Self { + let render_state = &state!().render_state; + + let shader_module = render_state + .device + .create_shader_module(wgpu::include_wgsl!("tilemap.wgsl")); + + let pipeline_layout = + render_state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Tilemap Render Pipeline Layout"), + bind_group_layouts: &[image_cache::Cache::bind_group_layout()], + push_constant_ranges: &[ + // Viewport + Autotiles + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX, + range: 0..(64 + 36), + }, + // Fragment + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::FRAGMENT, + range: (64 + 36)..(64 + 36 + 4), + }, + ], + }); + let pipeline = + render_state + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Tilemap Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: "vs_main", + buffers: &[Vertex::desc(), Instances::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader_module, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + ..render_state.target_format.into() + })], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + Self { pipeline } + } + + pub fn bind(render_pass: &mut wgpu::RenderPass<'_>) { + render_pass.set_pipeline(&TILEMAP_SHADER.pipeline) + } +} + +static TILEMAP_SHADER: Lazy = Lazy::new(Shader::new); diff --git a/src/graphics/primitives/tiles/tilemap.wgsl b/src/graphics/primitives/tiles/tilemap.wgsl new file mode 100644 index 00000000..9d3fe093 --- /dev/null +++ b/src/graphics/primitives/tiles/tilemap.wgsl @@ -0,0 +1,102 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, +} + +struct InstanceInput { + @location(2) tile_position: vec3, + @location(3) tile_id: i32, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + // todo: look into using multiple textures? +} + +struct PushConstants { + viewport: Viewport, + autotiles: Autotiles, + opacity: f32, +} + +struct Viewport { + proj: mat4x4, +} + +struct Autotiles { + animation_index: u32, + max_frame_count: u32, + frame_counts: array +} + +var push_constants: PushConstants; + + +@vertex +fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VertexOutput { + var out: VertexOutput; + + if instance.tile_id < 48 { + return out; + } + + let position = push_constants.viewport.proj * vec4(vertex.position.xy + (instance.tile_position.xy * 32.), 0.0, 1.0); + out.clip_position = vec4(position.xy, instance.tile_position.z, 1.0); + + let is_autotile = instance.tile_id < 384; + + // 1712 is the number of non-autotile tiles that can fit under the autotiles without wrapping around + let max_tiles_under_autotiles = i32(push_constants.autotiles.max_frame_count) * 1712; + let is_under_autotiles = !is_autotile && instance.tile_id - 384 < max_tiles_under_autotiles; + + var atlas_tile_position: vec2; + if is_autotile { + atlas_tile_position = vec2( + // If the tile is an autotile + f32((instance.tile_id - 48) % 8 * 32), + f32((instance.tile_id - 48) / 8 * 32) + ); + } else { + if is_under_autotiles { + atlas_tile_position = vec2( + // If the tile is not an autotile but is located underneath the autotiles in the atlas + f32((instance.tile_id % 8 + (instance.tile_id - 384) / 1712 * 8) * 32), + f32(((instance.tile_id - 384) / 8 % 214 + 42) * 32) + ); + } else { + atlas_tile_position = vec2( + // If the tile is not an autotile and is not located underneath the autotiles in the atlas + f32((instance.tile_id % 8 + ((instance.tile_id - 384 - max_tiles_under_autotiles) / 2048 + i32(push_constants.autotiles.max_frame_count)) * 8) * 32), + f32((instance.tile_id - 384 - max_tiles_under_autotiles) / 8 % 256 * 32) + ); + } + } + + if is_autotile { + let frame_count = push_constants.autotiles.frame_counts[instance.tile_id / 48 - 1]; + let frame = push_constants.autotiles.animation_index % frame_count; + atlas_tile_position.x += f32(frame * 256u); + } + let tex_size = vec2(textureDimensions(atlas)); + out.tex_coords = vertex.tex_coords + (atlas_tile_position / tex_size); + + return out; +} + +@group(0) @binding(0) +var atlas: texture_2d; +@group(0) @binding(1) +var atlas_sampler: sampler; + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + var color = textureSample(atlas, atlas_sampler, input.tex_coords); + color.a *= push_constants.opacity; + + if color.a <= 0.0 { + discard; + } + + return color; +} diff --git a/src/graphics/primitives/vertex.rs b/src/graphics/primitives/vertex.rs new file mode 100644 index 00000000..c911ddff --- /dev/null +++ b/src/graphics/primitives/vertex.rs @@ -0,0 +1,37 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use crate::prelude::*; + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] +pub struct Vertex { + pub position: glam::Vec3, + pub tex_coords: glam::Vec2, +} + +impl Vertex { + const ATTRIBS: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2]; + pub const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +} diff --git a/src/graphics/primitives/viewport.rs b/src/graphics/primitives/viewport.rs new file mode 100644 index 00000000..d33648c1 --- /dev/null +++ b/src/graphics/primitives/viewport.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use crossbeam::atomic::AtomicCell; + +#[derive(Debug)] +pub struct Viewport { + data: AtomicCell, +} + +impl Viewport { + pub fn new(proj: glam::Mat4) -> Self { + Self { + data: AtomicCell::new(proj), + } + } + + pub fn set_proj(&self, proj: glam::Mat4) { + self.data.store(proj); + } + + pub fn as_bytes(&self) -> [u8; std::mem::size_of::()] { + bytemuck::cast(self.data.load()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7b76b37e..39c1fa55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,21 +32,29 @@ #![warn(rust_2018_idioms)] #![warn( clippy::all, - // clippy::pedantic, clippy::panic, clippy::panic_in_result_fn, clippy::panicking_unwrap, - // clippy::unwrap_used, - clippy::unnecessary_wraps + clippy::unnecessary_wraps, + // unsafe code is sometimes fine but in general we don't want to use it. + unsafe_code, )] +// These may be turned on in the future. +// #![warn(clippy::unwrap, clippy::pedantic)] #![allow( clippy::missing_errors_doc, clippy::doc_markdown, clippy::missing_panics_doc, clippy::too_many_lines )] -#![deny(unsafe_code)] -#![feature(drain_filter, min_specialization)] +// You must provide a safety doc. DO NOT TURN OFF THESE LINTS. +#![forbid(clippy::missing_safety_doc, unsafe_op_in_unsafe_fn)] +// Okay, lemme run through *why* some of these are enabled +// 1) min_specialization +// min_specialization is used in alox-48 to deserialize extra data types. +// 2) int_roundings +// int_roundings is close to stabilization. +#![feature(min_specialization, int_roundings)] pub use prelude::*; @@ -54,12 +62,12 @@ pub use prelude::*; pub mod luminol; pub mod prelude; -/// The state Luminol saves on shutdown. -pub mod saved_state; /// Audio related structs and funtions. pub mod audio; +pub mod config; + pub mod cache; pub mod components; @@ -72,8 +80,6 @@ pub mod windows; /// Stack defined windows that edit values. pub mod modals; -/// Structs related to Luminol's internal data. -pub mod project; /// Tabs to be displayed in the center of Luminol. pub mod tabs; @@ -87,8 +93,9 @@ pub mod lumi; #[cfg(feature = "steamworks")] pub mod steam; +pub mod graphics; + pub use luminol::Luminol; -use saved_state::SavedState; use tabs::tab::Tab; /// Embedded icon 256x256 in size. @@ -114,10 +121,11 @@ pub enum Pencil { /// Passed to windows and widgets when updating. pub struct State { /// Filesystem to be passed around. - pub filesystem: filesystem::Filesystem, + pub filesystem: filesystem::project::FileSystem, /// The data cache. pub data_cache: data::Cache, pub image_cache: image_cache::Cache, + pub atlas_cache: atlas::Cache, /// Windows that are displayed. pub windows: window::Windows, /// Tabs that are displayed. @@ -126,10 +134,7 @@ pub struct State { pub audio: audio::Audio, /// Toasts to be displayed. pub toasts: Toasts, - /// The gl context. - pub gl: Arc, - /// State to be saved. - pub saved_state: AtomicRefCell, + pub render_state: egui_wgpu::RenderState, /// Toolbar state pub toolbar: AtomicRefCell, } @@ -138,17 +143,17 @@ static_assertions::assert_impl_all!(State: Send, Sync); impl State { /// Create a new UpdateInfo. - pub fn new(gl: Arc, state: SavedState) -> Self { + pub fn new(render_state: egui_wgpu::RenderState) -> Self { Self { - filesystem: filesystem::Filesystem::default(), + filesystem: filesystem::project::FileSystem::default(), data_cache: data::Cache::default(), image_cache: image_cache::Cache::default(), + atlas_cache: atlas::Cache::default(), windows: windows::window::Windows::default(), tabs: tab::Tabs::new("global_tabs", vec![Box::new(started::Tab::new())]), audio: audio::Audio::default(), toasts: Toasts::default(), - gl, - saved_state: AtomicRefCell::new(state), + render_state, toolbar: AtomicRefCell::default(), } } diff --git a/src/luminol.rs b/src/luminol.rs index b2918592..1e009038 100644 --- a/src/luminol.rs +++ b/src/luminol.rs @@ -28,7 +28,6 @@ use crate::prelude::*; /// The main Luminol struct. Handles rendering, GUI state, that sort of thing. pub struct Luminol { top_bar: TopBar, - style: Arc, lumi: Lumi, } @@ -41,18 +40,63 @@ impl Luminol { ) -> Self { let storage = cc.storage.unwrap(); - let state = eframe::get_value(storage, "SavedState").unwrap_or_default(); + if let Some(global_config) = eframe::get_value(storage, "SavedState") { + *global_config!() = global_config; + } let style = eframe::get_value(storage, "EguiStyle").map_or_else(|| cc.egui_ctx.style(), |s| s); cc.egui_ctx.set_style(style.clone()); - let info = State::new(cc.gl.as_ref().unwrap().clone(), state); + let info = State::new(cc.wgpu_render_state.clone().unwrap()); crate::set_state(info); + #[cfg(not(debug_assertions))] + state!() + .render_state + .device + .on_uncaptured_error(Box::new(|e| { + use std::fmt::Write; + + let mut message_description = String::new(); + match e { + wgpu::Error::OutOfMemory { source } => { + message_description.push_str("wgpu error: Out of memory\n"); + writeln!(message_description, "{source:#?}").unwrap(); + } + wgpu::Error::Validation { + source, + description, + } => { + message_description.push_str("wgpu error: Validation error\n"); + writeln!(message_description, "{source}").unwrap(); + writeln!(message_description, "---------").unwrap(); + writeln!(message_description, "{}", source.source().unwrap()).unwrap(); + writeln!(message_description, "---------").unwrap(); + writeln!(message_description, "{source:#?}").unwrap(); + writeln!(message_description, "---------").unwrap(); + message_description.push_str(&description); + } + } + rfd::MessageDialog::new() + .set_title("Luminol has crashed!") + .set_level(rfd::MessageLevel::Error) + .set_description(&message_description) + .show(); + + let backtrace = std::backtrace::Backtrace::force_capture(); + rfd::MessageDialog::new() + .set_title("Backtrace") + .set_level(rfd::MessageLevel::Error) + .set_description(&backtrace.to_string()) + .show(); + + std::process::abort(); + })); + if let Some(path) = try_load_path { state!() .filesystem - .try_open_project(path) + .load_project(path) .expect("failed to load project"); } @@ -60,7 +104,6 @@ impl Luminol { Self { top_bar: TopBar::default(), - style, lumi, } } @@ -69,12 +112,7 @@ impl Luminol { impl eframe::App for Luminol { /// Called by the frame work to save state before shutdown. fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value::( - storage, - "SavedState", - &state!().saved_state.borrow(), - ); - eframe::set_value::>(storage, "EguiStyle", &self.style); + eframe::set_value(storage, "SavedState", &*global_config!()); } /// Called each time the UI needs repainting, which may be many times per second. @@ -83,10 +121,18 @@ impl eframe::App for Luminol { if let Some(f) = i.raw.dropped_files.first() { let path = f.path.clone().expect("dropped file has no path"); - if let Err(e) = state!().filesystem.try_open_project(path) { + if let Err(e) = state!().filesystem.load_project(path) { state!() .toasts .error(format!("Error opening dropped project: {e}")) + } else { + state!().toasts.info(format!( + "Successfully opened {:?}", + state!() + .filesystem + .project_path() + .expect("project not open") + )); } } }); @@ -97,14 +143,16 @@ impl eframe::App for Luminol { // Turn off button frame. ui.visuals_mut().button_frame = false; // Show the bar - self.top_bar.ui(ui, &mut self.style, frame); + self.top_bar.ui(ui, frame); }); }); // Central panel with tabs. - egui::CentralPanel::default().show(ctx, |ui| { - state!().tabs.ui(ui); - }); + egui::CentralPanel::default() + .frame(egui::Frame::central_panel(&ctx.style()).inner_margin(0.)) + .show(ctx, |ui| { + ui.group(|ui| state!().tabs.ui(ui)); + }); // Update all windows. state!().windows.update(ctx); diff --git a/src/main.rs b/src/main.rs index d6111b1f..753ecdac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ // // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . -// +//cargo r // Additional permission under GNU GPL version 3 section 7 // // If you modify this Program, or any covered work, by linking or combining @@ -31,7 +31,7 @@ fn main() { rfd::MessageDialog::new() .set_title("Error") .set_level(rfd::MessageLevel::Error) - .set_description(&format!( + .set_description(format!( "Steam error: {e}\nPerhaps you want to compile yourself a free copy?" )) .show(); @@ -40,22 +40,37 @@ fn main() { #[cfg(debug_assertions)] std::thread::spawn(|| loop { + use std::fmt::Write; + std::thread::sleep(std::time::Duration::from_secs(5)); + let deadlocks = parking_lot::deadlock::check_deadlock(); if deadlocks.is_empty() { continue; } - println!("Luminol has deadlocked! Please file an issue."); - println!("{} deadlocks detected", deadlocks.len()); + rfd::MessageDialog::new() + .set_title("Fatal Error") + .set_level(rfd::MessageLevel::Error) + .set_description(&format!( + "Luminol has deadlocked! Please file an issue.\n{} deadlocks detected", + deadlocks.len() + )) + .show(); for (i, threads) in deadlocks.iter().enumerate() { - println!("Deadlock #{}", i); + let mut description = String::new(); for t in threads { - println!("Thread Id {:#?}", t.thread_id()); - println!("{:#?}", t.backtrace()); + writeln!(description, "Thread Id {:#?}", t.thread_id()).unwrap(); + writeln!(description, "{:#?}", t.backtrace()).unwrap(); } + rfd::MessageDialog::new() + .set_title(&format!("Deadlock #{i}")) + .set_level(rfd::MessageLevel::Error) + .set_description(&description) + .show(); } - std::process::exit(1); + + std::process::abort(); }); // Log to stdout (if you run with `RUST_LOG=debug`). @@ -78,6 +93,20 @@ fn main() { height: image.height(), rgba: image.into_bytes(), }), + wgpu_options: eframe::egui_wgpu::WgpuConfiguration { + supported_backends: eframe::wgpu::util::backend_bits_from_env() + .unwrap_or(eframe::wgpu::Backends::PRIMARY), + device_descriptor: luminol::Arc::new(|_| eframe::wgpu::DeviceDescriptor { + label: Some("luminol device descriptor"), + features: eframe::wgpu::Features::PUSH_CONSTANTS, + limits: eframe::wgpu::Limits { + max_push_constant_size: 128, + ..eframe::wgpu::Limits::default() + }, + }), + ..Default::default() + }, + app_id: Some("astrabit.luminol".to_string()), ..Default::default() }; diff --git a/src/prelude.rs b/src/prelude.rs index a562811c..db5b7894 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,33 +25,43 @@ pub use crate::audio; pub use crate::cache::*; pub use crate::components::*; +pub use crate::config; +pub use crate::graphics::*; pub use crate::modals::*; -pub use crate::project::*; pub use crate::tabs::*; pub use crate::windows::*; -pub use crate::filesystem::Filesystem; -pub use crate::project::CommandDB; -pub use crate::project::LocalConfig; +pub use crate::filesystem; +pub use crate::filesystem::FileSystem; pub use std::collections::HashMap; +pub use std::num::NonZeroUsize; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; -pub use atomic_refcell::{AtomicRefCell, AtomicRefMut}; +pub use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; +pub use once_cell::sync::{Lazy, OnceCell}; pub use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; pub use parking_lot::{MappedRwLockWriteGuard, RwLock, RwLockWriteGuard}; +pub use crate::State; + +pub use crate::command_db; +pub use crate::game_ini; +pub use crate::global_config; +pub use crate::project_config; pub use crate::state; pub use eframe::egui; -pub use eframe::egui_glow::glow; +pub use eframe::egui_wgpu; +pub use eframe::egui_wgpu::wgpu; + pub use egui::epaint; pub use egui::Color32; pub use egui::TextureOptions; pub use egui_extras::RetainedImage; -pub use crate::State; +pub use itertools::Itertools; pub use poll_promise::Promise; diff --git a/src/tabs/map.rs b/src/tabs/map.rs index cc20ebf8..af7872de 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -27,42 +27,255 @@ use egui::Pos2; use std::{cell::RefMut, collections::HashMap}; use crate::prelude::*; +use crate::Pencil; pub struct Tab { /// ID of the map that is being edited. - pub id: i32, - /// Selected layer. - pub selected_layer: usize, - /// Toggled layers. - pub toggled_layers: Vec, - /// The cursor position. - pub cursor_pos: Pos2, + pub id: usize, /// The tilemap. - pub tilemap: Tilemap, - /// The selected tile in the tile picker. - pub selected_tile: i16, - dragged_event: usize, + pub view: MapView, + pub tilepicker: Tilepicker, + dragging_event: bool, + drawing_shape: bool, event_windows: window::Windows, force_close: bool, + + /// When event dragging starts, this is set to the difference between + /// the dragged event's tile and the cursor position + event_drag_offset: Option, + + layer_cache: Vec, + + /// This cache is used by the depth-first search when using the fill brush + dfs_cache: Vec, + /// This is used to save a copy of the current layer when using the + /// rectangle or circle brush + brush_layer_cache: Vec, + /// When drawing with any brush, + /// this is set to the position of the original tile we began drawing on + drawing_shape_pos: Option, } impl Tab { /// Create a new map editor. - pub fn new(id: i32) -> Option { - Some(Self { + pub fn new(id: usize) -> Result { + let map = state!().data_cache.map(id); + let tilesets = state!().data_cache.tilesets(); + let tileset = &tilesets[map.tileset_id]; + + Ok(Self { id, - selected_layer: 0, - toggled_layers: Vec::new(), - cursor_pos: Pos2::ZERO, - tilemap: Tilemap::new(id).ok()?, - selected_tile: 0, - dragged_event: 0, + view: MapView::new(&map, tileset)?, + tilepicker: Tilepicker::new(tileset)?, + dragging_event: false, + drawing_shape: false, event_windows: window::Windows::default(), force_close: false, + + event_drag_offset: None, + + layer_cache: vec![0; map.data.xsize() * map.data.ysize()], + + dfs_cache: vec![false; map.data.xsize() * map.data.ysize()], + brush_layer_cache: vec![0; map.data.xsize() * map.data.ysize()], + drawing_shape_pos: None, }) } + + fn recompute_autotile(&self, map: &rpg::Map, position: (usize, usize, usize)) -> i16 { + if map.data[position] >= 384 { + return map.data[position]; + } + + let autotile = map.data[position] / 48; + if autotile == 0 { + return 0; + } + + let x_array: [i8; 8] = [-1, 0, 1, 1, 1, 0, -1, -1]; + let y_array: [i8; 8] = [-1, -1, -1, 0, 1, 1, 1, 0]; + + /* + * 765 + * 0 4 + * 123 + */ + let mut bitfield = 0u8; + + // Loop through the 8 neighbors of this position + for (x, y) in x_array.into_iter().zip(y_array.into_iter()) { + bitfield <<= 1; + // Out-of-bounds tiles always count as valid neighbors + let is_out_of_bounds = ((x == -1 && position.0 == 0) + || (x == 1 && position.0 + 1 == map.data.xsize())) + || ((y == -1 && position.1 == 0) || (y == 1 && position.1 + 1 == map.data.ysize())); + // Otherwise, we only consider neighbors that are autotiles of the same type + let is_same_autotile = !is_out_of_bounds + && map.data[( + if x == -1 { + position.0 - 1 + } else { + position.0 + x as usize + }, + if y == -1 { + position.1 - 1 + } else { + position.1 + y as usize + }, + position.2, + )] / 48 + == autotile; + + if is_out_of_bounds || is_same_autotile { + bitfield |= 1 + } + } + + // Check how many edges have valid neighbors + autotile * 48 + + match (bitfield & 0b01010101).count_ones() { + 4 => { + // If the autotile is surrounded on all 4 edges, + // then the autotile variant is one of the first 16, + // depending on which corners are surrounded + let tl = (bitfield & 0b10000000 == 0) as u8; + let tr = (bitfield & 0b00100000 == 0) as u8; + let br = (bitfield & 0b00001000 == 0) as u8; + let bl = (bitfield & 0b00000010 == 0) as u8; + tl | (tr << 1) | (br << 2) | (bl << 3) + } + + 3 => { + // Rotate the bitfield 90 degrees counterclockwise until + // the one edge that is not surrounded is at the left + let mut bitfield = bitfield; + let mut i = 16u8; + while bitfield & 0b00000001 != 0 { + bitfield = bitfield.rotate_left(2); + i += 4; + } + // Now, the variant is one of the next 16 + let tr = (bitfield & 0b00100000 == 0) as u8; + let br = (bitfield & 0b00001000 == 0) as u8; + i + (tr | (br << 1)) + } + + // Top and bottom edges + 2 if bitfield & 0b01000100 == 0b01000100 => 32, + + // Left and right edges + 2 if bitfield & 0b00010001 == 0b00010001 => 33, + + 2 => { + // Rotate the bitfield 90 degrees counterclockwise until + // the two edges that are surrounded are at the right and bottom + let mut bitfield = bitfield; + let mut i = 34u8; + while bitfield & 0b00010100 != 0b00010100 { + bitfield = bitfield.rotate_left(2); + i += 2; + } + let br = (bitfield & 0b00001000 == 0) as u8; + i + br + } + + 1 => { + // Rotate the bitfield 90 degrees clockwise until + // the edge is at the bottom + let mut bitfield = bitfield; + let mut i = 42u8; + while bitfield & 0b00000100 == 0 { + bitfield = bitfield.rotate_right(2); + i += 1; + } + i + } + + 0 => 46, + + _ => unreachable!(), + } as i16 + } + + fn set_tile(&self, map: &mut rpg::Map, tile: SelectedTile, position: (usize, usize, usize)) { + map.data[position] = tile.to_id(); + + for y in -1i8..=1i8 { + for x in -1i8..=1i8 { + // Don't check tiles that are out of bounds + if ((x == -1 && position.0 == 0) || (x == 1 && position.0 + 1 == map.data.xsize())) + || ((y == -1 && position.1 == 0) + || (y == 1 && position.1 + 1 == map.data.ysize())) + { + continue; + } + let position = ( + if x == -1 { + position.0 - 1 + } else { + position.0 + x as usize + }, + if y == -1 { + position.1 - 1 + } else { + position.1 + y as usize + }, + position.2, + ); + let tile_id = self.recompute_autotile(map, position); + map.data[position] = tile_id; + } + } + } + + fn add_event(&self, map: &mut rpg::Map) { + let mut first_vacant_id = 1; + let mut max_event_id = 0; + + for (_, event) in map.events.iter() { + if event.id == first_vacant_id { + first_vacant_id += 1; + } + max_event_id = event.id; + + if event.x == self.view.cursor_pos.x as i32 && event.y == self.view.cursor_pos.y as i32 + { + state!() + .toasts + .error("Cannot create event on an existing event's tile"); + return; + } + } + + // Try first to allocate the event number directly after the current highest one. + // However, valid event number range in RPG Maker XP and VX is 1-999. + let new_event_id = if max_event_id < 999 { + max_event_id + 1 + } + // Otherwise, we'll try to use a non-allocated event ID that isn't zero. + else if first_vacant_id <= 999 { + first_vacant_id + } else { + state!() + .toasts + .error("Event limit reached, please delete some events"); + return; + }; + + map.events.insert( + new_event_id, + rpg::Event::new( + self.view.cursor_pos.x as i32, + self.view.cursor_pos.y as i32, + new_event_id, + ), + ); + + self.event_windows + .add_window(event_edit::Window::new(new_event_id, self.id)); + } } impl tab::Tab for Tab { @@ -80,165 +293,568 @@ impl tab::Tab for Tab { } fn show(&mut self, ui: &mut egui::Ui) { - let state = state!(); - - // Get the map. - let mut map = state.data_cache.get_map(self.id); - let tileset = state.data_cache.tilesets(); - let tileset = &tileset[map.tileset_id as usize - 1]; - - // If there are no toggled layers (i.e we just loaded the map) - // then fill up the vector with `true`; - if self.toggled_layers.is_empty() { - self.toggled_layers = vec![true; map.data.zsize() + 3]; - self.selected_layer = map.data.zsize() + 1; - } - // Display the toolbar. egui::TopBottomPanel::top(format!("map_{}_toolbar", self.id)).show_inside(ui, |ui| { ui.horizontal_wrapped(|ui| { ui.add( - egui::Slider::new(&mut self.tilemap.scale, 15.0..=200.) + egui::Slider::new(&mut self.view.scale, 15.0..=300.) .text("Scale") .fixed_decimals(0), ); ui.separator(); - // Find the number of layers. - let layers = map.data.zsize(); ui.menu_button( // Format the text based on what layer is selected. - if self.selected_layer > layers { - "Events ⏷".to_string() - } else { - format!("Layer {} ⏷", self.selected_layer + 1) + match self.view.selected_layer { + SelectedLayer::Events => "Events ⏷".to_string(), + SelectedLayer::Tiles(layer) => format!("Layer {} ⏷", layer + 1), }, |ui| { // TODO: Add layer enable button // Display all layers. ui.columns(2, |columns| { columns[1].visuals_mut().button_frame = true; - columns[0].label("Panorama"); - columns[1].checkbox(&mut self.toggled_layers[layers + 1], "👁"); + columns[0].label(egui::RichText::new("Panorama").underline()); + columns[1].checkbox(&mut self.view.map.pano_enabled, "👁"); - for layer in 0..layers { + for (index, layer) in + self.view.map.enabled_layers.iter_mut().enumerate() + { columns[0].selectable_value( - &mut self.selected_layer, - layer, - format!("Layer {}", layer + 1), + &mut self.view.selected_layer, + SelectedLayer::Tiles(index), + format!("Layer {}", index + 1), ); - columns[1].checkbox(&mut self.toggled_layers[layer], "👁"); + columns[1].checkbox(layer, "👁"); } + // Display event layer. columns[0].selectable_value( - &mut self.selected_layer, - layers + 1, - "Events", + &mut self.view.selected_layer, + SelectedLayer::Events, + egui::RichText::new("Events").italics(), ); - columns[1].checkbox(&mut self.toggled_layers[layers], "👁"); + columns[1].checkbox(&mut self.view.event_enabled, "👁"); - columns[0].label("Fog"); - columns[1].checkbox(&mut self.toggled_layers[layers + 2], "👁"); + columns[0].label(egui::RichText::new("Fog").underline()); + columns[1].checkbox(&mut self.view.map.fog_enabled, "👁"); }); }, ); ui.separator(); - ui.checkbox(&mut self.tilemap.visible_display, "Display Visible Area"); - ui.checkbox(&mut self.tilemap.move_preview, "Preview event move routes"); + ui.checkbox(&mut self.view.visible_display, "Display Visible Area") + .on_hover_text("Display the visible area in-game (640x480)"); + ui.checkbox(&mut self.view.move_preview, "Preview event move routes") + .on_hover_text("Preview event page move routes"); + ui.checkbox(&mut self.view.snap_to_grid, "Snap to grid") + .on_hover_text("Snap's the viewport to the tile grid"); + ui.checkbox( + &mut self.view.darken_unselected_layers, + "Darken unselected layers", + ) + .on_disabled_hover_text("Toggles darkening unselected layers"); + + /* + if ui.button("Save map preview").clicked() { + self.tilemap.save_to_disk(); + } + if map.preview_move_route.is_some() - && ui.button("Clear move route preview").clicked() + && ui.button("Clear move route preview").clicked() { map.preview_move_route = None; } + */ }); }); // Display the tilepicker. + let spacing = ui.spacing(); + let tilepicker_default_width = 256. + + 3. * spacing.window_margin.left + + spacing.scroll_bar_inner_margin + + spacing.scroll_bar_width + + spacing.scroll_bar_outer_margin; egui::SidePanel::left(format!("map_{}_tilepicker", self.id)) - .default_width(256.) + .default_width(tilepicker_default_width) + .max_width(tilepicker_default_width) .show_inside(ui, |ui| { egui::ScrollArea::both().show(ui, |ui| { - self.tilemap.tilepicker(ui, &mut self.selected_tile); + self.tilepicker.ui(ui); + ui.separator(); }); }); egui::CentralPanel::default().show_inside(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| { - let response = self.tilemap.ui( + // Get the map. + let mut map = state!().data_cache.map(self.id); + + // Save the state of the selected layer into the cache + if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { + for x in 0..map.data.xsize() { + for y in 0..map.data.ysize() { + self.layer_cache[x + y * map.data.xsize()] = + map.data[(x, y, tile_layer)]; + } + } + } + + let response = self.view.ui( ui, &map, - &mut self.cursor_pos, - &self.toggled_layers, - self.selected_layer, + &self.tilepicker, self.dragging_event, + self.drawing_shape, + self.drawing_shape_pos, + matches!(state!().toolbar.borrow().pencil, Pencil::Pen), ); let layers_max = map.data.zsize(); - let map_x = self.cursor_pos.x as i32; - let map_y = self.cursor_pos.y as i32; + let map_x = self.view.cursor_pos.x as i32; + let map_y = self.view.cursor_pos.y as i32; + + if self.dragging_event && self.view.selected_event_id.is_none() { + self.dragging_event = false; + self.event_drag_offset = None; + } + + if self.drawing_shape && !response.dragged_by(egui::PointerButton::Primary) { + self.drawing_shape = false; + self.drawing_shape_pos = None; + } - if response.dragged() - && self.selected_layer < layers_max - && !ui.input(|i| i.modifiers.command) + if self.drawing_shape_pos.is_some() + && !response.dragged_by(egui::PointerButton::Primary) { - map.data[(map_x as usize, map_y as usize, self.selected_layer)] = - self.selected_tile + 384; - } else if self.selected_layer >= layers_max { - if response.double_clicked() { - if let Some((id, event)) = map - .events - .iter() - .find(|(_, event)| event.x == map_x && event.y == map_y) - { - self.event_windows.add_window(event_edit::Window::new( - id, - self.id, - event.clone(), - tileset.tileset_name.clone(), - )); - } else { - let id = map.events.vacant_key(); - let event = rpg::Event::new(map_x, map_y, id); - - map.events.insert(event.clone()); - - self.event_windows.add_window(event_edit::Window::new( - id, - self.id, - event, - tileset.tileset_name.clone(), - )); + self.drawing_shape_pos = None; + } + + if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { + // Tile drawing + let position = (map_x as usize, map_y as usize, tile_layer); + let initial_tile = SelectedTile::from_id(map.data[position]); + let left = self.tilepicker.selected_tiles_left; + let right = self.tilepicker.selected_tiles_right; + let top = self.tilepicker.selected_tiles_top; + let bottom = self.tilepicker.selected_tiles_bottom; + let width = right - left + 1; + let height = bottom - top + 1; + if response.dragged_by(egui::PointerButton::Primary) + && !ui.input(|i| i.modifiers.command) + { + match state!().toolbar.borrow().pencil { + Pencil::Pen => { + let drawing_shape_pos = + if let Some(drawing_shape_pos) = self.drawing_shape_pos { + drawing_shape_pos + } else { + self.drawing_shape_pos = Some(self.view.cursor_pos); + self.view.cursor_pos + }; + for y in 0..height { + for x in 0..width { + // Skip out-of-bounds tiles + if ((x == -1 && position.0 == 0) + || (x == 1 && position.0 + 1 == map.data.xsize())) + || ((y == -1 && position.1 == 0) + || (y == 1 && position.1 + 1 == map.data.ysize())) + { + continue; + } + + self.set_tile( + &mut map, + self.tilepicker.get_tile_from_offset( + x + (self.view.cursor_pos.x - drawing_shape_pos.x) + as i16, + y + (self.view.cursor_pos.y - drawing_shape_pos.y) + as i16, + ), + ( + position.0 + x as usize, + position.1 + y as usize, + tile_layer, + ), + ); + } + } + } + + Pencil::Fill + if initial_tile == self.tilepicker.get_tile_from_offset(0, 0) => {} + Pencil::Fill => { + // Use depth-first search to find all of the orthogonally + // contiguous matching tiles + let mut stack = vec![position; 1]; + let initial_x = position.0; + let initial_y = position.1; + while let Some(position) = stack.pop() { + self.set_tile( + &mut map, + self.tilepicker.get_tile_from_offset( + position.0 as i16 - initial_x as i16, + position.1 as i16 - initial_y as i16, + ), + position, + ); + self.dfs_cache[position.0 + position.1 * map.data.xsize()] = + true; + + let x_array: [i8; 4] = [-1, 1, 0, 0]; + let y_array: [i8; 4] = [0, 0, -1, 1]; + for (x, y) in x_array.into_iter().zip(y_array.into_iter()) { + // Don't search tiles that are out of bounds + if ((x == -1 && position.0 == 0) + || (x == 1 && position.0 + 1 == map.data.xsize())) + || ((y == -1 && position.1 == 0) + || (y == 1 && position.1 + 1 == map.data.ysize())) + { + continue; + } + + let position = ( + if x == -1 { + position.0 - 1 + } else { + position.0 + x as usize + }, + if y == -1 { + position.1 - 1 + } else { + position.1 + y as usize + }, + position.2, + ); + + // Don't search tiles that we've already searched before + // because that would cause an infinite loop + if self.dfs_cache + [position.0 + position.1 * map.data.xsize()] + { + continue; + } + + if SelectedTile::from_id(map.data[position]) == initial_tile + { + stack.push(position); + } + } + } + + for x in self.dfs_cache.iter_mut() { + *x = false; + } + } + + Pencil::Rectangle => { + if !self.drawing_shape { + // Save the current layer + for x in 0..map.data.xsize() { + for y in 0..map.data.ysize() { + self.brush_layer_cache[x + y * map.data.xsize()] = + map.data[(x, y, tile_layer)]; + } + } + self.drawing_shape = true; + } else { + // Restore the previously stored state of the current layer + for y in 0..map.data.ysize() { + for x in 0..map.data.xsize() { + map.data[(x, y, tile_layer)] = + self.brush_layer_cache[x + y * map.data.xsize()]; + } + } + } + + if let Some(drawing_shape_pos) = self.drawing_shape_pos { + let bounding_rect = egui::Rect::from_two_pos( + drawing_shape_pos, + self.view.cursor_pos, + ); + for y in (bounding_rect.min.y as usize) + ..=(bounding_rect.max.y as usize) + { + for x in (bounding_rect.min.x as usize) + ..=(bounding_rect.max.x) as usize + { + let position = (x, y, tile_layer); + self.set_tile( + &mut map, + self.tilepicker.get_tile_from_offset( + x as i16 - drawing_shape_pos.x as i16, + y as i16 - drawing_shape_pos.y as i16, + ), + position, + ); + } + } + } else { + self.drawing_shape_pos = Some(self.view.cursor_pos); + } + } + + Pencil::Circle => { + if !self.drawing_shape { + // Save the current layer + for x in 0..map.data.xsize() { + for y in 0..map.data.ysize() { + self.brush_layer_cache[x + y * map.data.xsize()] = + map.data[(x, y, tile_layer)]; + } + } + self.drawing_shape = true; + } else { + // Restore the previously stored state of the current layer + for y in 0..map.data.ysize() { + for x in 0..map.data.xsize() { + map.data[(x, y, tile_layer)] = + self.brush_layer_cache[x + y * map.data.xsize()]; + } + } + } + + // Use Bresenham's algorithm to draw the ellipse. + // We consider (x, y) to be the top-left corner of the tile at + // (x, y). + if let Some(drawing_shape_pos) = self.drawing_shape_pos { + let bounding_rect = egui::Rect::from_two_pos( + drawing_shape_pos, + self.view.cursor_pos, + ); + // Edge case: Bresenham's algorithm breaks down when drawing a + // 1x1 ellipse. + if drawing_shape_pos == self.view.cursor_pos { + self.set_tile( + &mut map, + self.tilepicker.get_tile_from_offset( + map_x as i16 - drawing_shape_pos.x as i16, + map_y as i16 - drawing_shape_pos.y as i16, + ), + (map_x as usize, map_y as usize, tile_layer), + ); + } else { + let bounding_rect = + bounding_rect.translate(egui::vec2(0.5, 0.5)); + + // Calculate where the center of the ellipse should be. + let x0 = bounding_rect.center().x; + let y0 = bounding_rect.center().y; + + // Calculate the radii of the ellipse along the + // x and y directions. + let rx = bounding_rect.width() / 2.; + let ry = bounding_rect.height() / 2.; + let rx2 = rx * rx; + let ry2 = ry * ry; + + // Let the "ellipse function" be defined as + // f(x, y) = b^2 x^2 + a^2 y^2 - a^2 b^2 + // where a is the x-radius of an ellipse centered at (0, 0) + // and b is the y-radius. + // This function is positive when (x, y) is outside the + // ellipse, negative when it's inside the ellipse and zero when + // it's exactly on the edge. + + // We'll start by drawing the part of the outer edge of the + // bottom-right quadrant of the ellipse where dy/dx >= -1, + // starting from the bottom of the ellipse and going to the + // right. + let mut x = if rx.floor() == rx { 0. } else { 0.5 }; + let mut y = ry; + + // Keep looping until dy/dx < -1. + while rx2 * y >= ry2 * x { + for i in ((-y).floor() as i32)..=(y.floor() as i32) { + let i = if y.floor() == y { + i as f32 + } else { + i as f32 + 0.5 + }; + for j in [x, -x] { + let x = (x0 + j).floor(); + let y = (y0 + i).floor(); + self.set_tile( + &mut map, + self.tilepicker.get_tile_from_offset( + x as i16 - drawing_shape_pos.x as i16, + y as i16 - drawing_shape_pos.y as i16, + ), + (x as usize, y as usize, tile_layer), + ); + } + } + + // The next tile will either be at (x + 1, y) or + // (x + 1, y - 1), whichever is closest to the actual edge + // of the ellipse. + // To determine which is closer, we evaluate the ellipse + // function at (x + 1, y - 0.5). + // If it's positive, then (x + 1, y - 1) is closer. + // If it's negative, then (x + 1, y) is closer. + let f = ry2 * (x + 1.).powi(2) + + rx2 * (y - 0.5).powi(2) + - rx2 * ry2; + if f > 0. { + y -= 1.; + } + x += 1.; + } + + // Now we draw the part of the outer edge of the + // bottom-right quadrant of the ellipse where dy/dx <= -1, + // starting from the right of the ellipse and going down. + let mut x = rx; + let mut y = if ry.floor() == ry { 0. } else { 0.5 }; + + // Keep looping until dy/dx > -1. + while rx2 * y <= ry2 * x { + for i in ((-x).floor() as i32)..=(x.floor() as i32) { + let i = if x.floor() == x { + i as f32 + } else { + i as f32 + 0.5 + }; + for j in [y, -y] { + let x = (x0 + i).floor(); + let y = (y0 + j).floor(); + self.set_tile( + &mut map, + self.tilepicker.get_tile_from_offset( + x as i16 - drawing_shape_pos.x as i16, + y as i16 - drawing_shape_pos.y as i16, + ), + (x as usize, y as usize, tile_layer), + ); + } + } + + // The next tile will either be at (x, y + 1) or + // (x - 1, y + 1), whichever is closest to the actual edge + // of the ellipse. + // To determine which is closer, we evaluate the ellipse + // function at (x - 0.5, y + 1). + // If it's positive, then (x - 1, y + 1) is closer. + // If it's negative, then (x, y + 1) is closer. + let f = ry2 * (x - 0.5).powi(2) + + rx2 * (y + 1.).powi(2) + - rx2 * ry2; + if f > 0. { + x -= 1.; + } + y += 1.; + } + } + } else { + self.drawing_shape_pos = Some(self.view.cursor_pos); + } + } + }; + } + } else if let Some(selected_event_id) = self.view.selected_event_id { + if response.double_clicked() + || (response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| i.key_pressed(egui::Key::Enter))) + { + // Double-click/press enter on events to edit them + if ui.input(|i| !i.modifiers.command) { + self.dragging_event = false; + self.event_drag_offset = None; + self.event_windows + .add_window(event_edit::Window::new(selected_event_id, self.id)); } + } + // Allow drag and drop to move events + else if !self.dragging_event + && self.view.selected_event_is_hovered + && response.drag_started_by(egui::PointerButton::Primary) + { + self.dragging_event = true; + } else if self.dragging_event + && !response.dragged_by(egui::PointerButton::Primary) + { self.dragging_event = false; - } else if response.drag_started() && response.clicked() { - if let Some((id, _)) = map - .events - .iter() - .find(|(_, event)| event.x == map_x && event.y == map_y) - { - self.dragged_event = id; - self.dragging_event = true; + self.event_drag_offset = None; + } + + // Press delete or backspace to delete the selected event + if response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| { + i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace) + }) + { + map.events.remove(selected_event_id); + let _ = self.view.events.try_remove(selected_event_id); + } + + if let Some(hover_tile) = self.view.hover_tile { + if self.dragging_event { + if let Some(selected_event) = map.events.get(selected_event_id) { + // If we just started dragging an event, save the offset between the + // cursor and the event's tile so that the event will be dragged + // with that offset from the cursor + if self.event_drag_offset.is_none() { + self.event_drag_offset = Some( + egui::Pos2::new( + selected_event.x as f32, + selected_event.y as f32, + ) - hover_tile, + ); + }; + } + + if let Some(offset) = self.event_drag_offset { + // If moving an event, move the dragged event's tile to the cursor + // after adjusting for drag offset, unless that would put the event + // on the same tile as an existing event + let adjusted_hover_tile = hover_tile + offset; + if !map.events.iter().any(|(_, e)| { + adjusted_hover_tile.x == e.x as f32 + && adjusted_hover_tile.y == e.y as f32 + }) { + if let Some(selected_event) = + map.events.get_mut(selected_event_id) + { + selected_event.x = adjusted_hover_tile.x as i32; + selected_event.y = adjusted_hover_tile.y as i32; + } + } + } } - } else if response.dragged() && self.dragging_event { - map.events[self.dragged_event].x = map_x; - map.events[self.dragged_event].y = map_y; - } else { + } + } else { + // Double-click/press enter on an empty space to add an event + // (hold shift to prevent events from being selected) + if response.double_clicked() + || (response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| i.key_pressed(egui::Key::Enter))) + { self.dragging_event = false; + self.event_drag_offset = None; + self.add_event(&mut map); } } - if ui.input(|i| { - i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace) - }) { - if let Some((id, _)) = map - .events - .iter() - .find(|(_, event)| event.x == map_x && event.y == map_y) - { - map.events.remove(id); + for (_, event) in map.events.iter_mut() { + event.extra_data.is_editor_open = false; + } + + // Write the buffered tile changes to the tilemap + if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { + for x in 0..map.data.xsize() { + for y in 0..map.data.ysize() { + let position = (x, y, tile_layer); + let new_tile_id = map.data[position]; + if new_tile_id != self.layer_cache[x + y * map.data.xsize()] { + self.view.map.set_tile(new_tile_id, position); + } + } } } }) diff --git a/src/tabs/started.rs b/src/tabs/started.rs index 4a654d4f..f7002963 100644 --- a/src/tabs/started.rs +++ b/src/tabs/started.rs @@ -95,15 +95,23 @@ impl tab::Tab for Tab { ui.heading("Recent"); - for path in &state.saved_state.borrow().recent_projects { + for path in &global_config!().recent_projects { if ui.button(path).clicked() { let path = path.clone(); self.load_project_promise = Some(Promise::spawn_local(async move { - if let Err(why) = state.filesystem.try_open_project(path) { + if let Err(why) = state.filesystem.load_project(path) { state .toasts .error(format!("Error loading the project: {why}")); + } else { + state!().toasts.info(format!( + "Successfully opened {:?}", + state!() + .filesystem + .project_path() + .expect("project not open") + )); } })); } diff --git a/src/tabs/tab.rs b/src/tabs/tab.rs index 74a92eb1..0766b228 100644 --- a/src/tabs/tab.rs +++ b/src/tabs/tab.rs @@ -25,12 +25,9 @@ use parking_lot::Mutex; use std::hash::Hash; -/// The tree type; -type Tree = egui_dock::Tree; - /// Helper struct for tabs. pub struct Tabs { - tree: Mutex>, + tree: Mutex>, id: egui::Id, } @@ -42,22 +39,20 @@ where pub fn new(id: impl Hash, tabs: Vec) -> Self { Self { id: egui::Id::new(id), - tree: Tree::new(tabs).into(), + tree: egui_dock::Tree::new(tabs).into(), } } /// Display all tabs. pub fn ui(&self, ui: &mut egui::Ui) { - ui.group(|ui| { - egui_dock::DockArea::new(&mut self.tree.lock()) - .id(self.id) - .show_inside( - ui, - &mut TabViewer { - marker: std::marker::PhantomData, - }, - ); - }); + egui_dock::DockArea::new(&mut self.tree.lock()) + .id(self.id) + .show_inside( + ui, + &mut TabViewer { + marker: std::marker::PhantomData, + }, + ); } /// Add a tab. @@ -73,12 +68,12 @@ where tree.push_to_focused_leaf(tab); } - /// Clean tabs by if they need the filesystem. + /// Removes tabs that the provided closure returns `false` when called. pub fn clean_tabs bool>(&self, mut f: F) { let mut tree = self.tree.lock(); for node in tree.iter_mut() { if let egui_dock::Node::Leaf { tabs, .. } = node { - tabs.drain_filter(&mut f); + tabs.retain_mut(&mut f) } } } diff --git a/src/windows/appearance.rs b/src/windows/appearance.rs new file mode 100644 index 00000000..73f2e645 --- /dev/null +++ b/src/windows/appearance.rs @@ -0,0 +1,93 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. +use crate::prelude::*; + +#[derive(Default)] +pub struct Window { + egui_settings_open: bool, +} + +impl super::Window for Window { + fn id(&self) -> egui::Id { + egui::Id::new("luminol_appearance_window") + } + + fn name(&self) -> String { + "Luminol Appearance".to_string() + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + egui::Window::new(self.name()).open(open).show(ctx, |ui| { + // Or these together so if one OR the other is true the window shows. + self.egui_settings_open = + ui.button("Egui Settings").clicked() || self.egui_settings_open; + + ui.menu_button("Catppuccin theme", |ui| { + if ui.button("Frappe").clicked() { + catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::FRAPPE); + } + if ui.button("Latte").clicked() { + catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::LATTE); + } + if ui.button("Macchiato").clicked() { + catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::MACCHIATO); + } + if ui.button("Mocha").clicked() { + catppuccin_egui::set_theme(ui.ctx(), catppuccin_egui::MOCHA); + } + }); + + let theme = &mut global_config!().theme; + ui.menu_button("Code Theme", |ui| { + theme.ui(ui); + + ui.label("Code sample"); + ui.label(syntax_highlighting::highlight( + ui.ctx(), + *theme, + r#" + class Foo < Array + end + def bar(baz) + end + print 1, 2.0 + puts [0x3, :4, '5'] + "#, + "rb", + )); + }); + + if ui + .button("Clear Loaded Textures") + .on_hover_text( + "You may need to reopen maps/windows for any changes to take effect.", + ) + .clicked() + { + state!().image_cache.clear(); + state!().atlas_cache.clear(); + } + }); + } +} diff --git a/src/windows/common_event_edit.rs b/src/windows/common_event_edit.rs index 530e7288..7b4a972b 100644 --- a/src/windows/common_event_edit.rs +++ b/src/windows/common_event_edit.rs @@ -59,7 +59,7 @@ impl window::Window for Window { .open(open) .show(ctx, |ui| { egui::SidePanel::left("common_events_side_panel").show_inside(ui, |ui| { - let common_events = state!().data_cache.commonevents(); + let common_events = state!().data_cache.common_events(); egui::ScrollArea::both().auto_shrink([false; 2]).show_rows( ui, @@ -150,7 +150,7 @@ impl tab::Tab for CommonEventTab { } if save_event { - let mut common_events = state!().data_cache.commonevents(); + let mut common_events = state!().data_cache.common_events(); common_events[self.event.id - 1] = self.event.clone(); } @@ -165,7 +165,7 @@ impl tab::Tab for CommonEventTab { .auto_shrink([false; 2]) .show(ui, |ui| { self.command_view - .ui(ui, &state!().data_cache.commanddb(), &mut self.event.list); + .ui(ui, &command_db!(), &mut self.event.list); }); } diff --git a/src/windows/config.rs b/src/windows/config_window.rs similarity index 95% rename from src/windows/config.rs rename to src/windows/config_window.rs index ae033ed0..58e5ae81 100644 --- a/src/windows/config.rs +++ b/src/windows/config_window.rs @@ -40,7 +40,7 @@ impl window::Window for Window { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()).open(open).show(ctx, |ui| { - let mut config = state!().data_cache.config(); + let mut config = project_config!(); ui.label("Project name"); ui.text_edit_singleline(&mut config.project_name); @@ -50,7 +50,7 @@ impl window::Window for Window { egui::ComboBox::from_label("RGSS Version") .selected_text(config.rgss_ver.to_string()) .show_ui(ui, |ui| { - for ver in RGSSVer::iter() { + for ver in config::RGSSVer::iter() { ui.selectable_value(&mut config.rgss_ver, ver, ver.to_string()); } }); diff --git a/src/windows/event_edit.rs b/src/windows/event_edit.rs index f904a7dd..cf740f20 100644 --- a/src/windows/event_edit.rs +++ b/src/windows/event_edit.rs @@ -22,45 +22,26 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -use egui_extras::RetainedImage; - use crate::prelude::*; /// The event editor window. pub struct Window { id: usize, - map_id: i32, + map_id: usize, selected_page: usize, - event: rpg::Event, - page_graphics: (Vec>>, Arc), + name: String, viewed_tab: u8, modals: (bool, bool, bool), } impl Window { /// Create a new event editor. - pub fn new(id: usize, map_id: i32, event: rpg::Event, tileset_name: String) -> Self { - let pages_graphics = event - .pages - .iter() - .map(|p| { - state!() - .image_cache - .load_egui_image("Graphics/Characters", &p.graphic.character_name) - .ok() - }) - .collect(); - let tileset_graphic = state!() - .image_cache - .load_egui_image("Graphics/Tilesets", tileset_name) - .unwrap(); - + pub fn new(id: usize, map_id: usize) -> Self { Self { id, map_id, selected_page: 0, - event, - page_graphics: (pages_graphics, tileset_graphic), + name: String::from("(unknown)"), viewed_tab: 2, modals: (false, false, false), } @@ -69,17 +50,27 @@ impl Window { impl window::Window for Window { fn name(&self) -> String { - format!( - "Event: {}, {} in Map {}", - self.event.name, self.id, self.map_id - ) + format!("Event: {}, {} in Map {}", self.name, self.id, self.map_id) } fn id(&self) -> egui::Id { - egui::Id::new("Event Editor") + egui::Id::new("luminol_event_edit") + .with(self.map_id) + .with(self.id) } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let mut map = state!().data_cache.map(self.map_id); + let event = match map.events.get_mut(self.id) { + Some(e) => e, + None => { + *open = false; + return; + } + }; + event.extra_data.is_editor_open = true; + self.name.clone_from(&event.name); + let mut win_open = true; egui::Window::new(self.name()) @@ -87,7 +78,7 @@ impl window::Window for Window { .open(&mut win_open) .show(ctx, |ui| { ui.horizontal(|ui| { - ui.text_edit_singleline(&mut self.event.name); + ui.text_edit_singleline(&mut event.name); ui.button("New page").clicked(); ui.button("Copy page").clicked(); @@ -98,7 +89,7 @@ impl window::Window for Window { ui.separator(); ui.horizontal(|ui| { - for (page, _) in self.event.pages.iter().enumerate() { + for (page, _) in event.pages.iter().enumerate() { if ui .selectable_value(&mut self.selected_page, page, page.to_string()) .clicked() @@ -118,7 +109,7 @@ impl window::Window for Window { ui.separator(); - let page = self.event.pages.get_mut(self.selected_page).unwrap(); + let page = event.pages.get_mut(self.selected_page).unwrap(); match self.viewed_tab { 0 => { @@ -301,62 +292,9 @@ impl window::Window for Window { }); }); } - 1 => { - let space = - ui.available_size_before_wrap() - ui.spacing().button_padding * 2.; - let (page_graphic, tileset_graphic) = &self.page_graphics; - - if if page.graphic.tile_id.is_positive() { - let ele = page.graphic.tile_id - 384; - - let tile_width = 32. / tileset_graphic.width() as f32; - let tile_height = 32. / tileset_graphic.height() as f32; - - let tile_x = - (ele as usize % (tileset_graphic.width() / 32)) as f32 * tile_width; - let tile_y = (ele as usize / (tileset_graphic.width() / 32)) as f32 - * tile_height; - - let uv = egui::Rect::from_min_size( - egui::pos2(tile_x, tile_y), - egui::vec2(tile_width, tile_height), - ); - - ui.add( - egui::ImageButton::new( - tileset_graphic.texture_id(ui.ctx()), - egui::vec2(space.x, space.x), - ) - .uv(uv), - ) - } else if let Some(ref tex) = page_graphic[self.selected_page] { - let cw = (tex.width() / 4) as f32; - let ch = (tex.height() / 4) as f32; - - let cx = (page.graphic.pattern as f32 * cw) / tex.width() as f32; - let cy = (((page.graphic.direction - 2) / 2) as f32 * ch) - / tex.height() as f32; - - let uv = egui::Rect::from_min_size( - egui::pos2(cx, cy), - egui::vec2(cw / tex.width() as f32, ch / tex.height() as f32), - ); - - ui.add( - egui::ImageButton::new( - tex.texture_id(ui.ctx()), - egui::vec2(space.x, ch * (space.x / cw)), - ) - .uv(uv), - ) - } else { - ui.button("Add image") - } - .clicked() - { - // TODO: Use modals for an image picker - } - } + + 1 => {} + 2 => { ui.vertical(|ui| { ui.group(|ui| { @@ -381,8 +319,8 @@ impl window::Window for Window { let cancel_clicked = ui.button("Cancel").clicked(); if apply_clicked || ok_clicked { - let mut map = state!().data_cache.get_map(self.map_id); - map.events[self.id] = self.event.clone(); + //let mut map = state!().data_cache.map(self.map_id); + //map.events[self.id] = event.clone(); } if cancel_clicked || ok_clicked { diff --git a/src/windows/global_config_window.rs b/src/windows/global_config_window.rs new file mode 100644 index 00000000..4a62973e --- /dev/null +++ b/src/windows/global_config_window.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. + +#[derive(Default)] +pub struct Window {} + +impl super::Window for Window { + fn name(&self) -> String { + "Luminol Preferences".to_string() + } + + fn id(&self) -> egui::Id { + egui::Id::new("luminol_preferences_window") + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + egui::Window::new(self.name()).open(open).show(ctx, |ui| {}); + } +} diff --git a/src/windows/graphic_picker.rs b/src/windows/graphic_picker.rs index ea56cce4..3d71c401 100644 --- a/src/windows/graphic_picker.rs +++ b/src/windows/graphic_picker.rs @@ -36,14 +36,11 @@ pub struct Window { impl Window { #[must_use] - pub fn new(icons: Vec) -> Self { + pub fn new(icons: Vec) -> Self { let mut retained_images = Vec::new(); - for icon_path in icons { - let icon_path = icon_path; - let split = icon_path.split('.').collect::>(); - - let icon_path = String::from(split[0]); + for mut icon_path in icons { + icon_path.set_extension(""); let image = match state!() .image_cache @@ -58,7 +55,7 @@ impl Window { } }; retained_images.push(Graphic { - name: icon_path, + name: icon_path.to_string(), image, }); } diff --git a/src/windows/items.rs b/src/windows/items.rs index 85a3e1d5..1f1dd1df 100644 --- a/src/windows/items.rs +++ b/src/windows/items.rs @@ -27,11 +27,10 @@ use crate::prelude::*; /// Database - Items management window. pub struct Window { // ? Items ? - items: NilPadded, + items: rpg::Items, selected_item: usize, // ? Icon Graphic Picker ? - icon_picker: graphic_picker::Window, icon_picker_open: bool, // ? Menu Sound Effect Picker ? @@ -42,21 +41,11 @@ pub struct Window { impl Default for Window { fn default() -> Self { let items = state!().data_cache.items().clone(); - let icon_paths = match state!().filesystem.dir_children_strings("Graphics/Icons") { - Ok(icons) => icons, - Err(why) => { - state!() - .toasts - .error(format!("Error while reading `Graphics/Icons`: {why}")); - Vec::new() - } - }; - let icon_picker = graphic_picker::Window::new(icon_paths); + Self { items, selected_item: 0, - icon_picker, icon_picker_open: false, menu_se_picker: sound_test::SoundTab::new(crate::audio::Source::SE, true), @@ -82,7 +71,7 @@ impl window::Window for Window { let _selected_item = &self.items[self.selected_item]; let animations = state!().data_cache.animations(); - let common_events = state!().data_cache.commonevents(); + let common_events = state!().data_cache.common_events(); /*#[allow(clippy::cast_sign_loss)] if animations @@ -129,11 +118,6 @@ impl window::Window for Window { egui::TextEdit::singleline(&mut selected_item.name), )); - ui.add(Field::new( - "Icon", - CallbackButton::new(selected_item.icon_name.clone()) - .on_click(|| self.icon_picker_open = !self.icon_picker_open), - )); ui.end_row(); ui.add(Field::new( @@ -142,57 +126,9 @@ impl window::Window for Window { )); ui.end_row(); - ui.add(Field::new( - "Scope", - EnumMenuButton::new(selected_item.scope, rpg::ItemScope::None, |scope| { - selected_item.scope = scope as i32; - }), - )); - - ui.add(Field::new( - "Occasion", - EnumMenuButton::new( - selected_item.occasion, - rpg::ItemOccasion::Always, - |occasion| { - selected_item.occasion = occasion as i32; - }, - ), - )); - ui.end_row(); - - ui.add(Field::new( - "User Animation", - NilPaddedMenu::new(&mut selected_item.animation1_id, &*animations), - )); - ui.add(Field::new( - "Target Animation", - NilPaddedMenu::new(&mut selected_item.animation2_id, &*animations), - )); - ui.end_row(); - - ui.add(Field::new( - "Menu Use SE", - CallbackButton::new(selected_item.menu_se.name.clone()).on_click(|| { - self.menu_se_picker_open = true; - }), - )); - ui.add(Field::new( - "Common Event", - NilPaddedMenu::new(&mut selected_item.common_event_id, &*common_events), - )); - ui.end_row(); - egui::Grid::new("item_edit_central_left_grid").show(ui, |_ui| {}); }); - if self.icon_picker_open { - self.icon_picker.show( - ctx, - &mut self.icon_picker_open, - &mut selected_item.icon_name, - ); - } if self.menu_se_picker_open { egui::Window::new("Menu Sound Effect Picker") .id(egui::Id::new("menu_se_picker")) diff --git a/src/windows/map_picker.rs b/src/windows/map_picker.rs index a28e36f4..80d01c41 100644 --- a/src/windows/map_picker.rs +++ b/src/windows/map_picker.rs @@ -23,7 +23,7 @@ // Program grant you additional permission to convey the resulting work. use crate::prelude::*; -use std::collections::HashMap; +use std::collections::{BTreeMap, BTreeSet}; /// The map picker window. /// Displays a list of maps in a tree. @@ -33,9 +33,9 @@ pub struct Window {} impl Window { fn render_submap( - id: i32, - children_data: &HashMap>, - mapinfos: &mut HashMap, + id: usize, + children_data: &BTreeMap>, + mapinfos: &mut rpg::MapInfos, ui: &mut egui::Ui, ) { // We get the map name. It's assumed that there is in fact a map with this ID in mapinfos. @@ -77,9 +77,10 @@ impl Window { } } - fn create_map_tab(id: i32) { - if let Some(m) = map::Tab::new(id) { - state!().tabs.add_tab(Box::new(m)); + fn create_map_tab(id: usize) { + match map::Tab::new(id) { + Ok(m) => state!().tabs.add_tab(Box::new(m)), + Err(e) => state!().toasts.error(e), } } } @@ -100,18 +101,14 @@ impl window::Window for Window { // Aquire the data cache. let mut mapinfos = state!().data_cache.mapinfos(); - // We sort maps by their order. - let mut sorted_maps = mapinfos.iter().collect::>(); - sorted_maps.sort_unstable(); - // We preprocess maps to figure out what has nodes and what doesn't. // This should result in an ordered hashmap of all the maps and their children. - let mut children_data: HashMap> = HashMap::new(); - for (id, map) in sorted_maps { + let mut children_data: BTreeMap<_, BTreeSet<_>> = BTreeMap::new(); + for (&id, map) in mapinfos.iter() { // Is there an entry for our parent? // If not, then just add a blank vector to it. - let children = children_data.entry(map.parent_id).or_default(); - children.push(*id); + let children = children_data.entry(map.parent_id).or_default(); // FIXME: this doesn't handle sorting properly + children.insert(id); } children_data.entry(0).or_default(); // If there is no `0` entry (i.e. there are no maps) then add one. @@ -121,8 +118,8 @@ impl window::Window for Window { .show(ui, |ui| { // There will always be a map `0`. // `0` is assumed to be the root map. - for id in children_data.get(&0).unwrap() { - Self::render_submap(*id, &children_data, &mut mapinfos, ui); + for &id in children_data.get(&0).unwrap() { + Self::render_submap(id, &children_data, &mut mapinfos, ui); } }); }) diff --git a/src/windows/misc.rs b/src/windows/misc.rs index d890bba9..3b4780cd 100644 --- a/src/windows/misc.rs +++ b/src/windows/misc.rs @@ -23,7 +23,7 @@ // Program grant you additional permission to convey the resulting work. use super::window::Window; - +use crate::prelude::*; /// Egui inspection window. #[derive(Default)] pub struct EguiInspection {} @@ -63,3 +63,22 @@ impl Window for EguiMemory { .show(ctx, |ui| ctx.memory_ui(ui)); } } + +#[derive(Default)] +pub struct FilesystemDebug {} + +impl Window for FilesystemDebug { + fn name(&self) -> String { + "Filesystem Debug".to_string() + } + + fn id(&self) -> egui::Id { + egui::Id::new("Filesystem Debug Window") + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + egui::Window::new(self.name()) + .open(open) + .show(ctx, |ui| state!().filesystem.debug_ui(ui)); + } +} diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 0931a21c..9d73ee0e 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -24,14 +24,16 @@ /// The about window. pub mod about; +pub mod appearance; /// The common event editor. pub mod common_event_edit; /// Config window -pub mod config; +pub mod config_window; /// Playtest console pub mod console; /// The event editor. pub mod event_edit; +pub mod global_config_window; /// The Graphic picker. pub mod graphic_picker; /// The item editor. diff --git a/src/windows/new_project.rs b/src/windows/new_project.rs index 11d0e287..7945a2b1 100644 --- a/src/windows/new_project.rs +++ b/src/windows/new_project.rs @@ -23,8 +23,7 @@ // Program grant you additional permission to convey the resulting work. use crate::prelude::*; - -use strum::IntoEnumIterator; +use config::{RGSSVer, RMVer}; use std::io::Read; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -34,6 +33,7 @@ use std::sync::Arc; pub struct Window { name: String, rgss_ver: RGSSVer, + editor_ver: RMVer, project_promise: Option>>, download_executable: bool, progress: Arc, @@ -54,6 +54,7 @@ impl Default for Window { Self { name: "My Project".to_string(), rgss_ver: RGSSVer::RGSS1, + editor_ver: RMVer::XP, project_promise: None, download_executable: false, progress: Arc::default(), @@ -145,8 +146,13 @@ impl window::Window for Window { } } else { if ui.button("Ok").clicked() { - let name = self.name.clone(); let rgss_ver = self.rgss_ver; + let config = config::project::Config { + project_name: self.name.clone(), + rgss_ver, + editor_ver: self.editor_ver, + ..Default::default() + }; let download_executable = self.download_executable && matches!( rgss_ver, @@ -161,8 +167,7 @@ impl window::Window for Window { self.project_promise = Some(poll_promise::Promise::spawn_local(async move { let state = state!(); - let result = - state.filesystem.try_create_project(name, rgss_ver).await; + let result = state.data_cache.create_project(config).await; if init_git && result.is_ok() { use std::process::Command; @@ -275,14 +280,19 @@ impl Window { .to_str() .ok_or(format!("Invalid file path {file_path:#?}"))?; - if file_path.is_empty() || state.filesystem.path_exists(file_path) { + if file_path.is_empty() + || state + .filesystem + .exists(file_path) + .map_err(|e| e.to_string())? + { continue; } if file.is_dir() { state .filesystem - .create_directory(file_path) + .create_dir(file_path) .map_err(|e| format!("Failed to create directory {file_path}: {e}"))?; } else { let mut bytes = Vec::new(); @@ -291,7 +301,7 @@ impl Window { .map_err(|e| format!("Failed to read file data {file_path}: {e}"))?; state .filesystem - .save_data(file_path, bytes) + .write(file_path, bytes) .map_err(|e| format!("Failed to save file data {file_path}: {e}"))?; } } diff --git a/src/windows/script_edit.rs b/src/windows/script_edit.rs index d7333635..3d637794 100644 --- a/src/windows/script_edit.rs +++ b/src/windows/script_edit.rs @@ -138,7 +138,7 @@ impl tab::Tab for ScriptTab { } fn show(&mut self, ui: &mut egui::Ui) { - let theme = state!().saved_state.borrow().theme; + let theme = global_config!().theme; ui.horizontal(|ui| { let mut save_script = false; diff --git a/src/windows/sound_test.rs b/src/windows/sound_test.rs index d86b7028..bc0edd3a 100644 --- a/src/windows/sound_test.rs +++ b/src/windows/sound_test.rs @@ -33,22 +33,23 @@ pub struct SoundTab { volume: u8, pitch: u8, selected_track: String, - folder_children: Vec, + folder_children: Vec, } impl SoundTab { /// Create a new SoundTab pub fn new(source: audio::Source, picker: bool) -> Self { + let folder_children = state!() + .filesystem + .read_dir(format!("Audio/{source}")) + .unwrap(); Self { picker, source, volume: 100, pitch: 100, selected_track: String::new(), - folder_children: state!() - .filesystem - .dir_children_strings(format!("Audio/{source}")) - .unwrap(), + folder_children, } } @@ -59,7 +60,7 @@ impl SoundTab { .show_inside(ui, |ui| { ui.vertical(|ui| { ui.horizontal(|ui| { - if ui.button("Play").clicked() && !self.selected_track.is_empty() { + if ui.button("Play").clicked() && !self.selected_track.as_str().is_empty() { let path = format!("Audio/{}/{}", self.source, &self.selected_track); let pitch = self.pitch; let volume = self.volume; @@ -133,8 +134,8 @@ impl SoundTab { if ui .selectable_value( &mut self.selected_track, - entry.clone(), - entry, + entry.file_name().to_string(), + entry.file_name(), ) .double_clicked() {