diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml new file mode 100644 index 0000000000..e5d6afb795 --- /dev/null +++ b/.github/workflows/artifacts.yml @@ -0,0 +1,60 @@ +# This workflow will create artifacts for all merges into master +# It will include linux, macos binaries with assets and an msi installer. + +name: Artifacts + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + release-macos: + runs-on: [macos-latest] + steps: + # LFS Checkout from Gitlab + - name: Gitlab LFS Checkout + run: | + mkdir -p $RUNNER_WORKSPACE/veloren + git init $RUNNER_WORKSPACE/veloren + cd $RUNNER_WORKSPACE/veloren + git remote add origin https://gitlab.com/veloren/veloren.git/ + git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +$GITHUB_SHA:refs/remotes/origin/$(echo $GITHUB_REF | cut -c 12-) + git checkout --progress --force -B $(echo $GITHUB_REF | cut -c 12-) refs/remotes/origin/$(echo $GITHUB_REF | cut -c 12-) + git log -1 + # Prepare toolchain + - name: Pull toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + # Cache + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + # Build & Package airshipper + - name: Build + run: cargo build + - name: Package + run: | + mkdir -p artifact/ + mkdir -p artifact/assets + cp -r assets/ artifact/assets/ + cp target/debug/veloren-server-cli artifact/ + cp target/debug/veloren-voxygen artifact/ + # Upload artifact + - name: Upload artifact + uses: actions/upload-artifact@v1 + with: + name: airshipper-macos + path: artifact/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ab4816508..813eabf4c0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -109,8 +109,8 @@ coverage: tags: - veloren-docker script: - - cargo tarpaulin -v - allow_failure: true + - echo "Workaround, tarpaulin fails due some rust files are already deleted, so we just stack tarpaulin. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail" + - cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v benchmarks: stage: post @@ -121,7 +121,18 @@ benchmarks: script: - unset DISABLE_GIT_LFS_CHECK - cargo bench + +localization-status: + variables: + GIT_DEPTH: 0 + stage: post + when: delayed + start_in: 5 seconds allow_failure: true + tags: + - veloren-docker + script: + - cargo test -q test_all_localizations -- --nocapture --ignored linux: stage: post @@ -170,5 +181,4 @@ windows: - assets/ - LICENSE expire_in: 1 week - # -- diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc81b6ee7..055492a259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added music system +- Added zoomable and rotatable minimap +- Added rotating orientation marker to main-map +- Added daily Mac builds +- Allow spawning individual pet species, not just generic body kinds. +- Configurable fonts +- Tanslation status tracking +- Added gamma setting +- Added new orc hairstyles +- Added sfx for wielding/unwielding weapons ### Changed +- Brighter / higher contrast main-map +- Removed highlighting of non-collectible sprites +- Fixed /give_exp ignoring player argument +- Extend run sfx to small animals to prevent sneak attacks by geese. + ### Removed ## [0.5.0] - 2019-01-31 diff --git a/Cargo.lock b/Cargo.lock index 98b1f66043..399d5b6a2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,11 @@ name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "backtrace" version = "0.3.40" @@ -324,6 +329,10 @@ dependencies = [ name = "cc" version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "cexpr" @@ -391,11 +400,6 @@ dependencies = [ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "claxon" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "clipboard-win" version = "2.2.0" @@ -1143,6 +1147,20 @@ dependencies = [ "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "git2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gl_generator" version = "0.11.0" @@ -1399,6 +1417,16 @@ dependencies = [ "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "image" version = "0.22.3" @@ -1475,6 +1503,14 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "jpeg-decoder" version = "0.1.16" @@ -1523,6 +1559,19 @@ name = "libc" version = "0.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libgit2-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "libssh2-sys 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libloading" version = "0.5.2" @@ -1532,6 +1581,30 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libssh2-sys" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "line_drawing" version = "0.7.0" @@ -1583,14 +1656,6 @@ name = "lzw" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "mach" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1666,23 +1731,6 @@ dependencies = [ "x11-dl 2.18.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "minimp3" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "minimp3-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "minimp3-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "mio" version = "0.6.21" @@ -1961,6 +2009,23 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "orbclient" version = "0.3.27" @@ -2555,15 +2620,13 @@ dependencies = [ [[package]] name = "rodio" -version = "0.9.0" -source = "git+https://github.com/RustAudio/rodio?rev=e5474a2#e5474a2ef15f2d0a3bae2538de159b6d3e5bdf79" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "cpal 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "hound 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", - "minimp3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2816,16 +2879,6 @@ name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "slice-deque" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "smallvec" version = "0.6.13" @@ -3154,6 +3207,16 @@ dependencies = [ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "uvth" version = "3.1.1" @@ -3164,6 +3227,11 @@ dependencies = [ "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vek" version = "0.9.11" @@ -3291,6 +3359,7 @@ dependencies = [ "gfx 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_device_gl 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_window_glutin 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "glsl-include 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere)", @@ -3300,7 +3369,7 @@ dependencies = [ "msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)", + "rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3624,6 +3693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atom 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3c86699c3f02778ec07158376991c8f783dd1f2f95c579ffaf0738dc984b2fe2" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" @@ -3654,7 +3724,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" "checksum clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f86c952727a495bda7abaf09bafdee1a939194dd793d9a8e26281df55ac43b00" "checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" @@ -3733,6 +3802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" "checksum gio 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2db9fad8f1b0d4c7338a210a6cbdf081dcc1a3c223718c698c4f313f6c288acb" "checksum gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a57872499171d279f8577ce83837da4cae62b08dd32892236ed67ab7ea61030" +"checksum git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c1af51ea8a906616af45a4ce78eacf25860f7a13ae7bf8a814693f0f4037a26" "checksum gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39a23d5e872a275135d66895d954269cf5e8661d234eb1c2480f4ce0d586acbd" "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" "checksum gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" @@ -3758,6 +3828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4be8aaefbe7545dc42ae925afb55a0098f226a3fe5ef721872806f44f57826" "checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" @@ -3767,6 +3838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" "checksum jpeg-decoder 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "c1aae18ffeeae409c6622c3b6a7ee49792a7e5a062eea1b135fbb74e301792ba" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" @@ -3774,7 +3846,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" +"checksum libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4870c781f6063efb83150cd22c1ddf6ecf58531419e7570cdcced46970f64a16" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +"checksum libssh2-sys 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "36aa6e813339d3a063292b77091dfbbb6152ff9006a459895fa5bebed7d34f10" +"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" @@ -3782,7 +3857,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum lz4-compress 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f966533a922a9bba9e95e594c1fdb3b9bf5fdcdb11e37e51ad84cd76e468b91" "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" -"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" @@ -3792,8 +3866,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0d977de9ee851a0b16e932979515c0f3da82403183879811bc97d50bd9cc50f7" "checksum minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)" = "" -"checksum minimp3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "542e9bed56860c5070a09939eee0e2df6f8f73f60304ddf56d620947e7017239" -"checksum minimp3-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c109ae05c00ad6e3a53fab101e2f234545bdd010f0fffd399355efaf70817817" "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -3821,6 +3893,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" "checksum objc_id 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" "checksum ogg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d79f1db9148be9d0e174bb3ac890f6030fcb1ed947267c5a91ee4c91b5a91e15" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" "checksum orbclient 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f8b18f57ab94fbd058e30aa57f712ec423c0bb7403f8493a6c58eef0c36d9402" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" @@ -3888,7 +3962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)" = "" +"checksum rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0e0dfa7c8b17c6428f6e992a22ea595922cc86f946191b6b59e7ce96b77262" "checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" "checksum roots 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e4c67c712ab62be58b24ab8960e2b95dd4ee00aac115c76f2709657821fe376d" "checksum rouille 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "112568052ec17fa26c6c11c40acbb30d3ad244bf3d6da0be181f5e7e42e5004f" @@ -3919,7 +3993,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5752e017e03af9d735b4b069f53b7a7fd90fefafa04d8bd0c25581b0bff437f" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7" "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" "checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" "checksum smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" @@ -3959,7 +4032,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" "checksum uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e59a167890d173eb0fcd7a1b99b84dc05c521ae8d76599130b8e19bef287abbf" +"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" "checksum vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "1eb3ca8ea588deba055424cc1a79a830428b2f6c270b8d8f91946f660fa4d8ee" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/assets/common/npc_names.json b/assets/common/npc_names.json index 4fec15bf01..f3fb272e08 100644 --- a/assets/common/npc_names.json +++ b/assets/common/npc_names.json @@ -19,6 +19,7 @@ "Colborn", "Dagfinn", "Dagrod", + "Digbod", "Dimian", "Domnhar", "Ebraheim", @@ -107,28 +108,34 @@ }, "species": { "danari": { + "keyword": "danari", "generic": "Danari" }, "dwarf": { + "keyword": "dwarf", "generic": "Dwarf" }, "elf": { + "keyword": "elf", "generic": "Elf" }, "human": { + "keyword": "human", "generic": "Human" }, "orc": { + "keyword": "orc", "generic": "Orc" }, "undead": { + "keyword": "undead", "generic": "Undead" } } }, "quadruped_medium": { "body": { - "keyword": "wolf", + "keyword": "quadruped_medium", "names": [ "Achak", "Adalwolf", @@ -237,34 +244,42 @@ }, "species": { "wolf": { + "keyword": "wolf", "generic": "Wolf" }, "saber": { + "keyword": "sabertooth", "generic": "Sabertooth Tiger" }, "viper": { + "keyword": "viper", "generic": "Lizard" }, "tuskram": { + "keyword": "tuskram", "generic": "Tusk Ram" }, "alligator": { + "keyword": "alligator", "generic": "Alligator" }, "monitor": { + "keyword": "monitor", "generic": "Monitor Lizard" }, "lion": { + "keyword": "lion", "generic": "Lion" }, "tarasque": { + "keyword": "tarasque", "generic": "Tarasque" } } }, "quadruped_small": { "body": { - "keyword": "pig", + "keyword": "quadruped_small", "names": [ "Acorn", "Adeline", @@ -372,102 +387,125 @@ }, "species": { "pig": { + "keyword": "pig", "generic": "Pig" }, "fox": { + "keyword": "fox", "generic": "Fox" }, "sheep": { + "keyword": "sheep", "generic": "Sheep" }, "boar": { + "keyword": "boar", "generic": "Boar" }, "jackalope": { + "keyword": "jackalope", "generic": "Jackalope" }, "skunk": { + "keyword": "skunk", "generic": "Skunk" }, "cat": { + "keyword": "cat", "generic": "Cat" }, "batfox": { + "keyword": "batfox", "generic": "Bat Fox" }, "raccoon": { + "keyword": "raccoon", "generic": "Raccoon" }, "quokka": { + "keyword": "quokka", "generic": "Quokka" }, "dodarock": { + "keyword": "dodarock", "generic": "Dodarock" }, "holladon": { + "keyword": "holladon", "generic": "Holladon" } } }, "bird_medium": { "body": { - "keyword": "duck", + "keyword": "bird_medium", "names": [ "Donald" ] }, "species": { "duck": { + "keyword": "duck", "generic": "Duck" }, "chicken": { + "keyword": "chicken", "generic": "Chicken" }, "goose": { + "keyword": "goose", "generic": "Goose" }, "peacock": { + "keyword": "peacock", "generic": "Peacock" } } }, "biped_large": { "body": { - "keyword": "giant", + "keyword": "biped_large", "names": [ "Leroy Brown" ] }, "species": { "giant": { + "keyword": "giant", "generic": "Giant" } } }, "critter": { "body": { - "keyword": "rat", + "keyword": "critter", "names": [ "Remy" ] }, "species": { "rat": { + "keyword": "rat", "generic": "Rat" }, "axolotl": { + "keyword": "axolotl", "generic": "Axolotl" }, "gecko": { + "keyword": "gecko", "generic": "Gecko" }, "turtle": { + "keyword": "turtle", "generic": "Turtle" }, "squirrel": { + "keyword": "squirrel", "generic": "Squirrel" }, "fungome": { + "keyword": "fungome", "generic": "Fungome" } } diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 8310f26c09..38349f287e 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -23,5 +23,17 @@ ], threshold: 0.5, ), + Wield(Sword): ( + files: [ + "voxygen.audio.sfx.weapon.sword_out", + ], + threshold: 0.5, + ), + Unwield(Sword): ( + files: [ + "voxygen.audio.sfx.weapon.sword_in", + ], + threshold: 0.5, + ), } ) \ No newline at end of file diff --git a/assets/voxygen/audio/sfx/weapon/sword_in.wav b/assets/voxygen/audio/sfx/weapon/sword_in.wav new file mode 100644 index 0000000000..c6bb047790 Binary files /dev/null and b/assets/voxygen/audio/sfx/weapon/sword_in.wav differ diff --git a/assets/voxygen/audio/sfx/weapon/sword_out.wav b/assets/voxygen/audio/sfx/weapon/sword_out.wav new file mode 100644 index 0000000000..4ca57c71b6 Binary files /dev/null and b/assets/voxygen/audio/sfx/weapon/sword_out.wav differ diff --git a/assets/voxygen/element/buttons/button.png b/assets/voxygen/element/buttons/button.png new file mode 100644 index 0000000000..0f29f105f4 Binary files /dev/null and b/assets/voxygen/element/buttons/button.png differ diff --git a/assets/voxygen/element/buttons/button_hover.png b/assets/voxygen/element/buttons/button_hover.png new file mode 100644 index 0000000000..a76572873c Binary files /dev/null and b/assets/voxygen/element/buttons/button_hover.png differ diff --git a/assets/voxygen/element/buttons/button_mmap_closed_hover.vox b/assets/voxygen/element/buttons/button_mmap_closed_hover.vox index e61ef968e1..eeb8895222 100644 Binary files a/assets/voxygen/element/buttons/button_mmap_closed_hover.vox and b/assets/voxygen/element/buttons/button_mmap_closed_hover.vox differ diff --git a/assets/voxygen/element/buttons/button_mmap_closed_press.vox b/assets/voxygen/element/buttons/button_mmap_closed_press.vox index 23cf970bfc..773b63e831 100644 Binary files a/assets/voxygen/element/buttons/button_mmap_closed_press.vox and b/assets/voxygen/element/buttons/button_mmap_closed_press.vox differ diff --git a/assets/voxygen/element/buttons/button_mmap_open.vox b/assets/voxygen/element/buttons/button_mmap_open.vox index 0419c94dd4..151a0f224f 100644 Binary files a/assets/voxygen/element/buttons/button_mmap_open.vox and b/assets/voxygen/element/buttons/button_mmap_open.vox differ diff --git a/assets/voxygen/element/buttons/button_mmap_open_hover.vox b/assets/voxygen/element/buttons/button_mmap_open_hover.vox index 88cb9c2c6d..471bd8c49e 100644 Binary files a/assets/voxygen/element/buttons/button_mmap_open_hover.vox and b/assets/voxygen/element/buttons/button_mmap_open_hover.vox differ diff --git a/assets/voxygen/element/buttons/button_mmap_open_press.vox b/assets/voxygen/element/buttons/button_mmap_open_press.vox index 59510d8f44..c20e0a9d13 100644 Binary files a/assets/voxygen/element/buttons/button_mmap_open_press.vox and b/assets/voxygen/element/buttons/button_mmap_open_press.vox differ diff --git a/assets/voxygen/element/buttons/button_press.png b/assets/voxygen/element/buttons/button_press.png new file mode 100644 index 0000000000..42c5b287f8 Binary files /dev/null and b/assets/voxygen/element/buttons/button_press.png differ diff --git a/assets/voxygen/element/frames/enemybar-0.vox b/assets/voxygen/element/buttons/indicator_mmap_small.vox similarity index 88% rename from assets/voxygen/element/frames/enemybar-0.vox rename to assets/voxygen/element/buttons/indicator_mmap_small.vox index ecf3ea214f..3be664e2f5 100644 Binary files a/assets/voxygen/element/frames/enemybar-0.vox and b/assets/voxygen/element/buttons/indicator_mmap_small.vox differ diff --git a/assets/voxygen/element/buttons/min_plus/mmap_button-min.vox b/assets/voxygen/element/buttons/min_plus/mmap_button-min.vox index 322487d85b..433c06b901 100644 Binary files a/assets/voxygen/element/buttons/min_plus/mmap_button-min.vox and b/assets/voxygen/element/buttons/min_plus/mmap_button-min.vox differ diff --git a/assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox b/assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox index 1b27aa4864..5dfa14ec30 100644 Binary files a/assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox and b/assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox differ diff --git a/assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox b/assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox index 6e4c11db7d..950abaa9b7 100644 Binary files a/assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox and b/assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox differ diff --git a/assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox b/assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox index df3b128c37..e1c2d3ca0a 100644 Binary files a/assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox and b/assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox differ diff --git a/assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox b/assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox index c481901020..460a2dab0d 100644 Binary files a/assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox and b/assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox differ diff --git a/assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox b/assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox index 48286d4bd0..62342c3be1 100644 Binary files a/assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox and b/assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox differ diff --git a/assets/voxygen/element/frames/banner.vox b/assets/voxygen/element/frames/banner.vox index 0934e4d89a..9039297d65 100644 Binary files a/assets/voxygen/element/frames/banner.vox and b/assets/voxygen/element/frames/banner.vox differ diff --git a/assets/voxygen/element/frames/banner_small_top.png b/assets/voxygen/element/frames/banner_small_top.png new file mode 100644 index 0000000000..6a207775e8 Binary files /dev/null and b/assets/voxygen/element/frames/banner_small_top.png differ diff --git a/assets/voxygen/element/frames/banner_top.png b/assets/voxygen/element/frames/banner_top.png new file mode 100644 index 0000000000..d3561d3a9c Binary files /dev/null and b/assets/voxygen/element/frames/banner_top.png differ diff --git a/assets/voxygen/element/frames/enemybar.png b/assets/voxygen/element/frames/enemybar.png index 246a1422f0..43413d347d 100644 Binary files a/assets/voxygen/element/frames/enemybar.png and b/assets/voxygen/element/frames/enemybar.png differ diff --git a/assets/voxygen/element/frames/enemybar_bg.png b/assets/voxygen/element/frames/enemybar_bg.png new file mode 100644 index 0000000000..c2c2146f5e Binary files /dev/null and b/assets/voxygen/element/frames/enemybar_bg.png differ diff --git a/assets/voxygen/element/frames/esc_menu.vox b/assets/voxygen/element/frames/esc_menu.vox index 028e990a29..dbb1f8e856 100644 Binary files a/assets/voxygen/element/frames/esc_menu.vox and b/assets/voxygen/element/frames/esc_menu.vox differ diff --git a/assets/voxygen/element/frames/mmap.vox b/assets/voxygen/element/frames/mmap.vox index 30fda28eb2..d9cc731b67 100644 Binary files a/assets/voxygen/element/frames/mmap.vox and b/assets/voxygen/element/frames/mmap.vox differ diff --git a/assets/voxygen/element/icons/elf_m.png b/assets/voxygen/element/icons/elf_m.png index aa835dfbb2..4c61719f8c 100644 Binary files a/assets/voxygen/element/icons/elf_m.png and b/assets/voxygen/element/icons/elf_m.png differ diff --git a/assets/voxygen/element/misc_bg/textbox_bot.png b/assets/voxygen/element/misc_bg/textbox_bot.png new file mode 100644 index 0000000000..c28435b703 Binary files /dev/null and b/assets/voxygen/element/misc_bg/textbox_bot.png differ diff --git a/assets/voxygen/element/misc_bg/textbox_mid.png b/assets/voxygen/element/misc_bg/textbox_mid.png new file mode 100644 index 0000000000..8c09cd780b Binary files /dev/null and b/assets/voxygen/element/misc_bg/textbox_mid.png differ diff --git a/assets/voxygen/element/misc_bg/textbox_top.png b/assets/voxygen/element/misc_bg/textbox_top.png new file mode 100644 index 0000000000..8431fc58ce Binary files /dev/null and b/assets/voxygen/element/misc_bg/textbox_top.png differ diff --git a/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf b/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf new file mode 100644 index 0000000000..4cd13e883a Binary files /dev/null and b/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf differ diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index fcf50d5956..96d7344654 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -17,6 +17,28 @@ VoxygenLocalization( language_identifier: "en", ), convert_utf8_to_ascii: false, + fonts: { + "opensans": Font ( + asset_key: "voxygen.font.OpenSans-Regular", + scale_ratio: 1.0, + ), + "metamorph": Font ( + asset_key: "voxygen.font.Metamorphous-Regular", + scale_ratio: 1.0, + ), + "alkhemi": Font ( + asset_key: "voxygen.font.Alkhemikal", + scale_ratio: 1.0, + ), + "wizard": Font ( + asset_key: "voxygen.font.wizard", + scale_ratio: 1.0, + ), + "cyri": Font ( + asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended", + scale_ratio: 1.0, + ), + }, string_map: { /// Start Common section // Texts used in multiple locations with the same formatting @@ -186,6 +208,7 @@ Enjoy your stay in the World of Veloren."#, "hud.settings.view_distance": "View Distance", "hud.settings.maximum_fps": "Maximum FPS", "hud.settings.fov": "Field of View (deg)", + "hud.settings.gamma": "Gamma", "hud.settings.antialiasing_mode": "AntiAliasing Mode", "hud.settings.cloud_rendering_mode": "Cloud Rendering Mode", "hud.settings.fluid_rendering_mode": "Fluid Rendering Mode", diff --git a/assets/voxygen/i18n/fr_FR.ron b/assets/voxygen/i18n/fr_FR.ron index d1cbbb75c6..6d5c4d581c 100644 --- a/assets/voxygen/i18n/fr_FR.ron +++ b/assets/voxygen/i18n/fr_FR.ron @@ -4,7 +4,29 @@ VoxygenLocalization( language_name: "Français", language_identifier: "fr_FR", ), - convert_utf8_to_ascii: true, + convert_utf8_to_ascii: false, + fonts: { + "opensans": Font ( + asset_key: "voxygen.font.OpenSans-Regular", + scale_ratio: 1.0, + ), + "metamorph": Font ( + asset_key: "voxygen.font.Metamorphous-Regular", + scale_ratio: 1.0, + ), + "alkhemi": Font ( + asset_key: "voxygen.font.Alkhemikal", + scale_ratio: 1.0, + ), + "wizard": Font ( + asset_key: "voxygen.font.wizard", + scale_ratio: 1.0, + ), + "cyri": Font ( + asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended", + scale_ratio: 0.9, + ), + }, string_map: { // Common texts used in multiple locations "common.username": "pseudo", @@ -16,7 +38,7 @@ VoxygenLocalization( "common.languages": "Langues", "common.interface": "Interface", "common.gameplay": "Gameplay", - "common.controls": "Controles", + "common.controls": "Contrôles", "common.video": "Video", "common.sound": "Audio", "common.resume": "Reprendre", diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 4669b9202e..285a5d3646 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -3,9 +3,9 @@ uniform sampler2D t_noise; const float CLOUD_AVG_HEIGHT = 1025.0; const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0; const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 50.0; -const float CLOUD_THRESHOLD = 0.3; +const float CLOUD_THRESHOLD = 0.25; const float CLOUD_SCALE = 5.0; -const float CLOUD_DENSITY = 80.0; +const float CLOUD_DENSITY = 100.0; float vsum(vec3 v) { return v.x + v.y + v.z; @@ -21,22 +21,27 @@ vec2 cloud_at(vec3 pos) { float value = ( 0.0 + texture(t_noise, scaled_pos * 0.0003 + tick_offs).x - + texture(t_noise, scaled_pos * 0.0009 - tick_offs).x * 0.5 - + texture(t_noise, scaled_pos * 0.0025 - time_of_day.x * 0.0002).x * 0.25 - + texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.15 - + texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.1 + + texture(t_noise, scaled_pos * 0.0015 - tick_offs * 2.0).x * 0.5 + //+ texture(t_noise, scaled_pos * 0.0025 - time_of_day.x * 0.0002).x * 0.25 + //+ texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.15 + //+ texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.2 ) / 3.0; + value += (0.0 + + texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.25 + + texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.15 + ) * value; + float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - CLOUD_AVG_HEIGHT) / 400.0, 0.0) * CLOUD_DENSITY; - const float SHADE_GRADIENT = 1.8 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN); - float shade = ((pos.z - CLOUD_AVG_HEIGHT) * SHADE_GRADIENT + 0.5); + const float SHADE_GRADIENT = 1.5 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN); + float shade = ((pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN)) * 2.5 + 0.7; return vec2(shade, density / (1.0 + vsum(abs(pos - cam_pos.xyz)) / 5000)); } vec4 get_cloud_color(vec3 dir, vec3 origin, float time_of_day, float max_dist, float quality) { - const int ITERS = 10; + const int ITERS = 12; const float INCR = 1.0 / ITERS; float mind = (CLOUD_HEIGHT_MIN - origin.z) / dir.z; @@ -61,6 +66,10 @@ vec4 get_cloud_color(vec3 dir, vec3 origin, float time_of_day, float max_dist, f passthrough *= 1.0 - integral; cloud_shade = mix(cloud_shade, sample.x, passthrough * integral); dist += INCR * delta; + + if (passthrough < 0.05 || (passthrough > 0.8 && dist > (maxd + mind) * 0.7)) { + break; + } } } diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index 471320a7ef..e0e97949e2 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -12,4 +12,5 @@ uniform u_globals { uvec4 light_shadow_count; uvec4 medium; ivec4 select_pos; + vec4 gamma; }; diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index dcc1733bd0..5b3869c8ac 100644 --- a/assets/voxygen/shaders/include/sky.glsl +++ b/assets/voxygen/shaders/include/sky.glsl @@ -70,10 +70,7 @@ void get_sun_diffuse(vec3 norm, float time_of_day, out vec3 light, out vec3 diff float sun_light = get_sun_brightness(sun_dir); float moon_light = get_moon_brightness(moon_dir); - // clamp() changed to max() as sun_dir.z is produced from a cos() function and therefore never greater than 1 - vec3 sun_color = get_sun_color(sun_dir); - vec3 moon_color = get_moon_color(moon_dir); vec3 sun_chroma = sun_color * sun_light; @@ -139,7 +136,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, vec3 origin, vec3 f_pos, float q vec3 moon_halo = pow(max(dot(dir, -moon_dir) + 0.1, 0.0), 8.0) * MOON_HALO_COLOR; vec3 moon_surf = pow(max(dot(dir, -moon_dir) - 0.001, 0.0), 3000.0) * MOON_SURF_COLOR; - vec3 moon_light = clamp(moon_halo + moon_surf, vec3(0), vec3(clamp(dir.z * 3.0, 0, 1))); + vec3 moon_light = clamp(moon_halo + moon_surf, vec3(0), vec3(max(dir.z * 3.0, 0))); // Replaced all clamp(sun_dir, 0, 1) with max(sun_dir, 0) because sun_dir is calculated from sin and cos, which are never > 1 @@ -188,7 +185,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, vec3 origin, vec3 f_pos, float q // Clouds clouds = get_cloud_color(dir, origin, time_of_day, f_dist, quality); - clouds.rgb *= get_sun_brightness(sun_dir) * (sun_halo * 2.5 + get_sun_color(sun_dir)) + get_moon_brightness(moon_dir) * (moon_halo * 80.0 + get_moon_color(moon_dir)); + clouds.rgb *= get_sun_brightness(sun_dir) * (sun_halo * 1.5 + get_sun_color(sun_dir)) + get_moon_brightness(moon_dir) * (moon_halo * 80.0 + get_moon_color(moon_dir)); if (f_dist > 5000.0) { sky_color += sun_light + moon_light; diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index 4193bda525..cd0c87a772 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -45,7 +45,7 @@ void main() { //hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0); //vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a); - vec4 final_color = aa_color; + vec4 final_color = pow(aa_color, gamma); if (medium.x == 1u) { final_color *= vec4(0.2, 0.2, 0.8, 1.0); diff --git a/assets/voxygen/shaders/ui-frag.glsl b/assets/voxygen/shaders/ui-frag.glsl index 2d77dd3ef7..a0c7e99904 100644 --- a/assets/voxygen/shaders/ui-frag.glsl +++ b/assets/voxygen/shaders/ui-frag.glsl @@ -20,7 +20,8 @@ void main() { if (f_mode == uint(0)) { tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); // Image - } else if (f_mode == uint(1)) { + // HACK: bit 0 is set for both ordinary and north-facing images. + } else if ((f_mode & uint(1)) == uint(1)) { tgt_color = f_color * texture(u_tex, f_uv); // 2D Geometry } else if (f_mode == uint(2)) { diff --git a/assets/voxygen/shaders/ui-vert.glsl b/assets/voxygen/shaders/ui-vert.glsl index d583e30220..57d7230d6b 100644 --- a/assets/voxygen/shaders/ui-vert.glsl +++ b/assets/voxygen/shaders/ui-vert.glsl @@ -4,6 +4,7 @@ in vec2 v_pos; in vec2 v_uv; +in vec2 v_center; in vec4 v_color; in uint v_mode; @@ -19,15 +20,31 @@ flat out uint f_mode; out vec4 f_color; void main() { - f_uv = v_uv; f_color = v_color; if (w_pos.w == 1.0) { + f_uv = v_uv; // Fixed scale In-game element vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0); gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0); + } else if (v_mode == uint(3)) { + // HACK: North facing source rectangle. + vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2])); + mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y); + f_uv = v_center + look_at * (v_uv - v_center); + gl_Position = vec4(v_pos, 0.0, 1.0); + } else if (v_mode == uint(5)) { + // HACK: North facing target rectangle. + f_uv = v_uv; + float aspect_ratio = screen_res.x / screen_res.y; + vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2])); + mat2 look_at = mat2(look_at_dir.y, -look_at_dir.x, look_at_dir.x, look_at_dir.y); + vec2 v_len = v_pos - v_center; + vec2 v_proj = look_at * vec2(v_len.x, v_len.y / aspect_ratio); + gl_Position = vec4(v_center + vec2(v_proj.x, v_proj.y * aspect_ratio), 0.0, 1.0); } else { // Interface element + f_uv = v_uv; gl_Position = vec4(v_pos, 0.0, 1.0); } f_mode = v_mode; diff --git a/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox b/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox index 416c565e93..32d5c915f0 100644 Binary files a/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox and b/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox differ diff --git a/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox b/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox new file mode 100644 index 0000000000..be756669b3 Binary files /dev/null and b/assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox differ diff --git a/assets/voxygen/voxel/figure/beard/human/human-0.vox b/assets/voxygen/voxel/figure/beard/human/human-0.vox index 227e10ef98..1139a44247 100644 Binary files a/assets/voxygen/voxel/figure/beard/human/human-0.vox and b/assets/voxygen/voxel/figure/beard/human/human-0.vox differ diff --git a/assets/voxygen/voxel/figure/hair/elf/male-2.vox b/assets/voxygen/voxel/figure/hair/elf/male-2.vox index e31e36c26d..af213dc98f 100644 Binary files a/assets/voxygen/voxel/figure/hair/elf/male-2.vox and b/assets/voxygen/voxel/figure/hair/elf/male-2.vox differ diff --git a/assets/voxygen/voxel/figure/hair/human/male-20.vox b/assets/voxygen/voxel/figure/hair/human/male-20.vox index e31e36c26d..af213dc98f 100644 Binary files a/assets/voxygen/voxel/figure/hair/human/male-20.vox and b/assets/voxygen/voxel/figure/hair/human/male-20.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female-0.vox b/assets/voxygen/voxel/figure/hair/orc/female-0.vox new file mode 100644 index 0000000000..e7d9e67262 Binary files /dev/null and b/assets/voxygen/voxel/figure/hair/orc/female-0.vox differ diff --git a/assets/voxygen/element/buttons/map_indicator.vox b/assets/voxygen/voxel/figure/hair/orc/female-1.vox similarity index 92% rename from assets/voxygen/element/buttons/map_indicator.vox rename to assets/voxygen/voxel/figure/hair/orc/female-1.vox index 7dc7315dd0..e2af621c57 100644 Binary files a/assets/voxygen/element/buttons/map_indicator.vox and b/assets/voxygen/voxel/figure/hair/orc/female-1.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female-2.vox b/assets/voxygen/voxel/figure/hair/orc/female-2.vox new file mode 100644 index 0000000000..8d55eea91f Binary files /dev/null and b/assets/voxygen/voxel/figure/hair/orc/female-2.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female-3.vox b/assets/voxygen/voxel/figure/hair/orc/female-3.vox new file mode 100644 index 0000000000..3c4315624d Binary files /dev/null and b/assets/voxygen/voxel/figure/hair/orc/female-3.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female-4.vox b/assets/voxygen/voxel/figure/hair/orc/female-4.vox new file mode 100644 index 0000000000..f7746ba774 Binary files /dev/null and b/assets/voxygen/voxel/figure/hair/orc/female-4.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female-5.vox b/assets/voxygen/voxel/figure/hair/orc/female-5.vox new file mode 100644 index 0000000000..0990e1c374 Binary files /dev/null and b/assets/voxygen/voxel/figure/hair/orc/female-5.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female-6.vox b/assets/voxygen/voxel/figure/hair/orc/female-6.vox new file mode 100644 index 0000000000..30e4602f09 Binary files /dev/null and b/assets/voxygen/voxel/figure/hair/orc/female-6.vox differ diff --git a/assets/voxygen/voxel/figure/hair/orc/female.vox b/assets/voxygen/voxel/figure/hair/orc/female.vox deleted file mode 100644 index a888fb8288..0000000000 Binary files a/assets/voxygen/voxel/figure/hair/orc/female.vox and /dev/null differ diff --git a/assets/voxygen/voxel/humanoid_head_manifest.ron b/assets/voxygen/voxel/humanoid_head_manifest.ron index c599e1dfbc..9e67fcb95b 100644 --- a/assets/voxygen/voxel/humanoid_head_manifest.ron +++ b/assets/voxygen/voxel/humanoid_head_manifest.ron @@ -27,6 +27,7 @@ Some(("figure.hair.human.male-20", (-3, -4, -7))), ], beard: [ + None, Some(("figure.beard.human.human-0", (4, 6, -2))), Some(("figure.beard.human.human-1", (5, 10, -2))), Some(("figure.beard.human.human-2", (3, 7, -3))), @@ -92,17 +93,24 @@ ], ), (Orc, Female): ( - offset: (-8.0, -3.0, -6.0), - head: ("figure.head.orc.female", (0, 2, 0)), - eyes: ("figure.eyes.orc.female-0", (3, 9, 2)), + offset: (-8.0, -2.5, -6.0), + head: ("figure.head.orc.female", (0, 1, 0)), + eyes: ("figure.eyes.orc.female-0", (3, 8, 2)), hair: [ - Some(("figure.hair.orc.female", (5, -2, 0))), + Some(("figure.hair.orc.female-0", (-2, -8, 0))), + Some(("figure.hair.orc.female-1", (-2, -8, 0))), + Some(("figure.hair.orc.female-2", (-2, -8, 0))), + Some(("figure.hair.orc.female-3", (-2, -8, -4))), + Some(("figure.hair.orc.female-4", (-2, -8, 0))), + Some(("figure.hair.orc.female-5", (-2, -8, -4))), + Some(("figure.hair.orc.female-6", (-2, -8, -4))), ], beard: [None], accessory: [ None, - Some(("figure.accessory.orc.earring-female-0", (2, 5, 1))), - Some(("figure.accessory.orc.warpaint-female-0", (3, 5, 1))), + Some(("figure.accessory.orc.earring-female-0", (2, 4, 1))), + Some(("figure.accessory.orc.warpaint-female-0", (-2, -4, -7))), + Some(("figure.accessory.orc.warpaint-female-1", (-2, -4, -7))), ], ), (Elf, Male): ( @@ -239,6 +247,7 @@ Some(("figure.hair.danari.male-1", (3, 1, 2))), ], beard: [ + None, Some(("figure.beard.danari.danari-0", (4, 6, -1))), ], accessory: [ diff --git a/assets/world/structure/dungeon/meso_sewer_temple.vox b/assets/world/structure/dungeon/meso_sewer_temple.vox index 5ccd7bf955..a2b5f911aa 100644 Binary files a/assets/world/structure/dungeon/meso_sewer_temple.vox and b/assets/world/structure/dungeon/meso_sewer_temple.vox differ diff --git a/common/build.rs b/common/build.rs index a56637b973..68d0eae9fb 100644 --- a/common/build.rs +++ b/common/build.rs @@ -8,6 +8,8 @@ use std::{ fn main() { // Get the current githash + // Note: It will compare commits. As long as the commits do not diverge from the + // server no version change will be detected. match Command::new("git") .args(&[ "log", @@ -15,6 +17,7 @@ fn main() { "1", "--pretty=format:%h/%cd", "--date=format:%Y-%m-%d-%H:%M", + "--abbrev=8", ]) .output() { diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index ac3569cb1d..f75acce151 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -63,6 +63,7 @@ pub struct AllBodies { impl core::ops::Index for AllBodies { type Output = BodyMeta; + #[inline] fn index(&self, index: NpcKind) -> &Self::Output { match index { NpcKind::Humanoid => &self.humanoid.body, @@ -101,9 +102,9 @@ impl Body { // TODO: Improve these values (some might be reliant on more info in inner type) match self { Body::Humanoid(_) => 0.5, - Body::QuadrupedSmall(_) => 0.6, + Body::QuadrupedSmall(_) => 0.3, Body::QuadrupedMedium(_) => 0.9, - Body::Critter(_) => 0.5, + Body::Critter(_) => 0.2, Body::BirdMedium(_) => 0.5, Body::FishMedium(_) => 0.5, Body::Dragon(_) => 2.5, @@ -113,6 +114,9 @@ impl Body { Body::Object(_) => 0.3, } } + + // Note: currently assumes sphericality + pub fn height(&self) -> f32 { self.radius() * 2.0 } } impl Component for Body { diff --git a/common/src/comp/body/biped_large.rs b/common/src/comp/body/biped_large.rs index 2b70d721ff..e32e647b04 100644 --- a/common/src/comp/body/biped_large.rs +++ b/common/src/comp/body/biped_large.rs @@ -10,11 +10,20 @@ impl Body { pub fn random() -> Self { let mut rng = thread_rng(); let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); - let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &species) + } + + #[inline] + pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self { + let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap(); Self { species, body_type } } } +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::BipedLarge(body) } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Species { @@ -29,10 +38,11 @@ pub struct AllSpecies { pub giant: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { type Output = SpeciesMeta; - fn index(&self, index: Species) -> &Self::Output { + #[inline] + fn index(&self, &index: &'a Species) -> &Self::Output { match index { Species::Giant => &self.giant, } @@ -41,6 +51,14 @@ impl core::ops::Index for AllSpecies { pub const ALL_SPECIES: [Species; 1] = [Species::Giant]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/bird_medium.rs b/common/src/comp/body/bird_medium.rs index b09d8cc562..477250c767 100644 --- a/common/src/comp/body/bird_medium.rs +++ b/common/src/comp/body/bird_medium.rs @@ -10,11 +10,20 @@ impl Body { pub fn random() -> Self { let mut rng = thread_rng(); let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); - let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &species) + } + + #[inline] + pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self { + let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap(); Self { species, body_type } } } +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::BirdMedium(body) } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Species { @@ -35,10 +44,11 @@ pub struct AllSpecies { pub peacock: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { type Output = SpeciesMeta; - fn index(&self, index: Species) -> &Self::Output { + #[inline] + fn index(&self, &index: &'a Species) -> &Self::Output { match index { Species::Duck => &self.duck, Species::Chicken => &self.chicken, @@ -55,6 +65,14 @@ pub const ALL_SPECIES: [Species; 4] = [ Species::Peacock, ]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/critter.rs b/common/src/comp/body/critter.rs index 6849db47e7..d72145a7e9 100644 --- a/common/src/comp/body/critter.rs +++ b/common/src/comp/body/critter.rs @@ -10,11 +10,20 @@ impl Body { pub fn random() -> Self { let mut rng = thread_rng(); let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); - let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &species) + } + + #[inline] + pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self { + let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap(); Self { species, body_type } } } +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::Critter(body) } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Species { @@ -37,10 +46,11 @@ pub struct AllSpecies { pub fungome: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { type Output = SpeciesMeta; - fn index(&self, index: Species) -> &Self::Output { + #[inline] + fn index(&self, &index: &'a Species) -> &Self::Output { match index { Species::Rat => &self.rat, Species::Axolotl => &self.axolotl, @@ -61,6 +71,14 @@ pub const ALL_SPECIES: [Species; 6] = [ Species::Fungome, ]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/humanoid.rs b/common/src/comp/body/humanoid.rs index 9e77190436..557956e790 100644 --- a/common/src/comp/body/humanoid.rs +++ b/common/src/comp/body/humanoid.rs @@ -24,19 +24,24 @@ impl Body { pub fn random() -> Self { let mut rng = thread_rng(); let race = *(&ALL_RACES).choose(&mut rng).unwrap(); - let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &race) + } + + #[inline] + pub fn random_with(rng: &mut impl Rng, &race: &Race) -> Self { + let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap(); Self { race, body_type, - chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(), - belt: *(&ALL_BELTS).choose(&mut rng).unwrap(), - pants: *(&ALL_PANTS).choose(&mut rng).unwrap(), - hand: *(&ALL_HANDS).choose(&mut rng).unwrap(), - foot: *(&ALL_FEET).choose(&mut rng).unwrap(), - shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(), + chest: *(&ALL_CHESTS).choose(rng).unwrap(), + belt: *(&ALL_BELTS).choose(rng).unwrap(), + pants: *(&ALL_PANTS).choose(rng).unwrap(), + hand: *(&ALL_HANDS).choose(rng).unwrap(), + foot: *(&ALL_FEET).choose(rng).unwrap(), + shoulder: *(&ALL_SHOULDERS).choose(rng).unwrap(), hair_style: rng.gen_range(0, race.num_hair_styles(body_type)), beard: rng.gen_range(0, race.num_beards(body_type)), - eyebrows: *(&ALL_EYEBROWS).choose(&mut rng).unwrap(), + eyebrows: *(&ALL_EYEBROWS).choose(rng).unwrap(), accessory: rng.gen_range(0, race.num_accessories(body_type)), hair_color: rng.gen_range(0, race.num_hair_colors()) as u8, skin: rng.gen_range(0, race.num_skin_colors()) as u8, @@ -58,6 +63,10 @@ impl Body { } } +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::Humanoid(body) } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Race { @@ -82,10 +91,11 @@ pub struct AllSpecies { pub undead: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Race> for AllSpecies { type Output = SpeciesMeta; - fn index(&self, index: Race) -> &Self::Output { + #[inline] + fn index(&self, &index: &'a Race) -> &Self::Output { match index { Race::Danari => &self.danari, Race::Dwarf => &self.dwarf, @@ -106,6 +116,14 @@ pub const ALL_RACES: [Race; 6] = [ Race::Undead, ]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Race; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_RACES.iter().copied() } +} + // Hair Colors pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [ (198, 169, 113), // Philosopher's Grey @@ -113,10 +131,10 @@ pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [ //(228, 208, 147), // Gold Blonde //(228, 223, 141), // Platinum Blonde (199, 131, 58), // Summer Blonde - (107, 76, 51), // Oak Brown - //(203, 154, 98), // Light Brown - (64, 32, 18), // Chocolate Brown - (86, 72, 71), // Ash Brown + (107, 76, 51), // Oak Skin4 + //(203, 154, 98), // Light Skin4 + (64, 32, 18), // Skin7 Skin4 + (86, 72, 71), // Ash Skin4 (57, 56, 61), // Raven Black (101, 83, 95), // Matte Purple (101, 57, 90), // Witch Purple @@ -130,10 +148,10 @@ pub const DWARF_HAIR_COLORS: [(u8, u8, u8); 20] = [ (228, 208, 147), // Gold Blonde (228, 223, 141), // Platinum Blonde (199, 131, 58), // Summer Blonde - (107, 76, 51), // Oak Brown - (203, 154, 98), // Light Brown - (64, 32, 18), // Chocolate Brown - (86, 72, 71), // Ash Brown + (107, 76, 51), // Oak Skin4 + (203, 154, 98), // Light Skin4 + (64, 32, 18), // Skin7 Skin4 + (86, 72, 71), // Ash Skin4 (57, 56, 61), // Raven Black (101, 83, 95), // Matte Purple (101, 57, 90), // Witch Purple @@ -154,10 +172,10 @@ pub const ELF_HAIR_COLORS: [(u8, u8, u8); 23] = [ (228, 208, 147), // Gold Blonde (228, 223, 141), // Platinum Blonde (199, 131, 58), // Summer Blonde - (107, 76, 51), // Oak Brown - (203, 154, 98), // Light Brown - (64, 32, 18), // Chocolate Brown - (86, 72, 71), // Ash Brown + (107, 76, 51), // Oak Skin4 + (203, 154, 98), // Light Skin4 + (64, 32, 18), // Skin7 Skin4 + (86, 72, 71), // Ash Skin4 (57, 56, 61), // Raven Black (101, 83, 95), // Matte Purple (101, 57, 90), // Witch Purple @@ -177,10 +195,10 @@ pub const HUMAN_HAIR_COLORS: [(u8, u8, u8); 21] = [ (228, 208, 147), // Gold Blonde (228, 223, 141), // Platinum Blonde (199, 131, 58), // Summer Blonde - (107, 76, 51), // Oak Brown - (203, 154, 98), // Light Brown - (64, 32, 18), // Chocolate Brown - (86, 72, 71), // Ash Brown + (107, 76, 51), // Oak Skin4 + (203, 154, 98), // Light Skin4 + (64, 32, 18), // Skin7 Skin4 + (86, 72, 71), // Ash Skin4 (57, 56, 61), // Raven Black (101, 83, 95), // Matte Purple (101, 57, 90), // Witch Purple @@ -197,11 +215,11 @@ pub const HUMAN_HAIR_COLORS: [(u8, u8, u8); 21] = [ ]; pub const ORC_HAIR_COLORS: [(u8, u8, u8); 10] = [ (66, 66, 59), // Wise Grey - //(107, 76, 51), // Oak Brown - //(203, 154, 98), // Light Brown - (64, 32, 18), // Chocolate Brown - (54, 30, 26), // Dark Chocolate - (86, 72, 71), // Ash Brown + //(107, 76, 51), // Oak Skin4 + //(203, 154, 98), // Light Skin4 + (64, 32, 18), // Skin7 Skin4 + (54, 30, 26), // Dark Skin7 + (86, 72, 71), // Ash Skin4 (57, 56, 61), // Raven Black (101, 83, 95), // Matte Purple (101, 57, 90), // Witch Purple @@ -214,10 +232,10 @@ pub const UNDEAD_HAIR_COLORS: [(u8, u8, u8); 21] = [ (228, 208, 147), // Gold Blonde //(228, 223, 141), // Platinum Blonde (199, 131, 58), // Summer Blonde - (107, 76, 51), // Oak Brown - (203, 154, 98), // Light Brown - (64, 32, 18), // Chocolate Brown - (86, 72, 71), // Ash Brown + (107, 76, 51), // Oak Skin4 + (203, 154, 98), // Light Skin4 + (64, 32, 18), // Skin7 Skin4 + (86, 72, 71), // Ash Skin4 (57, 56, 61), // Raven Black (101, 83, 95), // Matte Purple (101, 57, 90), // Witch Purple @@ -243,30 +261,59 @@ pub const DANARI_SKIN_COLORS: [Skin; 4] = [ Skin::DanariThree, Skin::DanariFour, ]; -pub const DWARF_SKIN_COLORS: [Skin; 5] = [ - Skin::Pale, - Skin::White, - Skin::Tanned, +pub const DWARF_SKIN_COLORS: [Skin; 14] = [ + Skin::Skin1, + Skin::Skin2, + Skin::Skin3, + Skin::Skin4, + Skin::Skin5, + Skin::Skin6, + Skin::Skin7, + Skin::Skin8, + Skin::Skin9, + Skin::Skin10, + Skin::Skin11, + Skin::Skin12, Skin::Iron, Skin::Steel, ]; -pub const ELF_SKIN_COLORS: [Skin; 7] = [ - Skin::Pale, +pub const ELF_SKIN_COLORS: [Skin; 14] = [ + Skin::Skin1, + Skin::Skin2, + Skin::Skin3, + Skin::Skin5, + Skin::Skin6, + Skin::Skin7, + Skin::Skin8, + Skin::Skin9, + Skin::Skin10, + Skin::Skin11, + Skin::Skin12, Skin::ElfOne, Skin::ElfTwo, Skin::ElfThree, - Skin::White, - Skin::Tanned, - Skin::TannedBrown, ]; -pub const HUMAN_SKIN_COLORS: [Skin; 5] = [ - Skin::Pale, - Skin::White, - Skin::Tanned, - Skin::TannedBrown, - Skin::TannedDarkBrown, +pub const HUMAN_SKIN_COLORS: [Skin; 18] = [ + Skin::Skin1, + Skin::Skin2, + Skin::Skin3, + Skin::Skin4, + Skin::Skin5, + Skin::Skin6, + Skin::Skin7, + Skin::Skin8, + Skin::Skin9, + Skin::Skin10, + Skin::Skin11, + Skin::Skin12, + Skin::Skin13, + Skin::Skin14, + Skin::Skin15, + Skin::Skin16, + Skin::Skin17, + Skin::Skin18, ]; -pub const ORC_SKIN_COLORS: [Skin; 4] = [Skin::OrcOne, Skin::OrcTwo, Skin::OrcThree, Skin::Brown]; +pub const ORC_SKIN_COLORS: [Skin; 4] = [Skin::OrcOne, Skin::OrcTwo, Skin::OrcThree, Skin::OrcFour]; pub const UNDEAD_SKIN_COLORS: [Skin; 3] = [Skin::UndeadOne, Skin::UndeadTwo, Skin::UndeadThree]; // Eye colors @@ -275,22 +322,31 @@ pub const DANARI_EYE_COLORS: [EyeColor; 3] = [ EyeColor::LoyalBrown, EyeColor::ViciousRed, ]; -pub const DWARF_EYE_COLORS: [EyeColor; 3] = [ +pub const DWARF_EYE_COLORS: [EyeColor; 4] = [ EyeColor::CuriousGreen, EyeColor::LoyalBrown, EyeColor::NobleBlue, + EyeColor::CornflowerBlue, ]; -pub const ELF_EYE_COLORS: [EyeColor; 3] = [ +pub const ELF_EYE_COLORS: [EyeColor; 4] = [ EyeColor::NobleBlue, + EyeColor::CornflowerBlue, EyeColor::CuriousGreen, EyeColor::LoyalBrown, ]; -pub const HUMAN_EYE_COLORS: [EyeColor; 3] = [ +pub const HUMAN_EYE_COLORS: [EyeColor; 4] = [ EyeColor::NobleBlue, + EyeColor::CornflowerBlue, EyeColor::CuriousGreen, EyeColor::LoyalBrown, ]; -pub const ORC_EYE_COLORS: [EyeColor; 2] = [EyeColor::LoyalBrown, EyeColor::ExoticPurple]; +pub const ORC_EYE_COLORS: [EyeColor; 5] = [ + EyeColor::LoyalBrown, + EyeColor::ExoticPurple, + EyeColor::AmberOrange, + EyeColor::PineGreen, + EyeColor::CornflowerBlue, +]; pub const UNDEAD_EYE_COLORS: [EyeColor; 5] = [ EyeColor::ViciousRed, EyeColor::PumpkinOrange, @@ -347,7 +403,7 @@ impl Race { self.skin_colors() .get(val as usize) .copied() - .unwrap_or(Skin::Tanned) + .unwrap_or(Skin::Skin3) } pub fn num_skin_colors(self) -> u8 { self.skin_colors().len() as u8 } @@ -371,7 +427,7 @@ impl Race { (Race::Elf, BodyType::Male) => 4, (Race::Human, BodyType::Female) => 19, (Race::Human, BodyType::Male) => 17, - (Race::Orc, BodyType::Female) => 1, + (Race::Orc, BodyType::Female) => 7, (Race::Orc, BodyType::Male) => 8, (Race::Undead, BodyType::Female) => 4, (Race::Undead, BodyType::Male) => 3, @@ -388,7 +444,7 @@ impl Race { (Race::Elf, BodyType::Male) => 1, (Race::Human, BodyType::Female) => 1, (Race::Human, BodyType::Male) => 1, - (Race::Orc, BodyType::Female) => 3, + (Race::Orc, BodyType::Female) => 4, (Race::Orc, BodyType::Male) => 5, (Race::Undead, BodyType::Female) => 1, (Race::Undead, BodyType::Male) => 1, @@ -516,6 +572,10 @@ pub enum EyeColor { MagicPurple = 7, ToxicGreen = 8, ExoticPurple = 9, + SulfurYellow = 10, + AmberOrange = 11, + PineGreen = 12, + CornflowerBlue = 13, } impl EyeColor { pub fn light_rgb(self) -> Rgb { @@ -530,6 +590,10 @@ impl EyeColor { EyeColor::MagicPurple => Rgb::new(137, 4, 177), EyeColor::ToxicGreen => Rgb::new(1, 223, 1), EyeColor::ExoticPurple => Rgb::new(95, 32, 111), + EyeColor::SulfurYellow => Rgb::new(235, 198, 94), + EyeColor::AmberOrange => Rgb::new(137, 46, 1), + EyeColor::PineGreen => Rgb::new(0, 78, 56), + EyeColor::CornflowerBlue => Rgb::new(18, 66, 90), } } @@ -545,6 +609,10 @@ impl EyeColor { EyeColor::MagicPurple => Rgb::new(110, 3, 143), EyeColor::ToxicGreen => Rgb::new(1, 185, 1), EyeColor::ExoticPurple => Rgb::new(69, 23, 80), + EyeColor::SulfurYellow => Rgb::new(209, 176, 84), + EyeColor::AmberOrange => Rgb::new(112, 40, 1), + EyeColor::PineGreen => Rgb::new(0, 54, 38), + EyeColor::CornflowerBlue => Rgb::new(13, 47, 64), } } @@ -562,12 +630,12 @@ pub const ALL_ACCESSORIES: [Accessory; 2] = [Accessory::Nothing, Accessory::Some #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Skin { - Pale = 0, - White = 1, - Tanned = 2, - Brown = 3, - TannedBrown = 4, - TannedDarkBrown = 5, + Skin1 = 0, + Skin2 = 1, + Skin3 = 2, + Skin4 = 3, + Skin5 = 4, + Skin6 = 5, Iron = 6, Steel = 7, DanariOne = 8, @@ -583,16 +651,41 @@ pub enum Skin { UndeadOne = 18, UndeadTwo = 19, UndeadThree = 20, + Skin7 = 21, + Skin8 = 22, + Skin9 = 23, + Skin10 = 24, + Skin11 = 25, + Skin12 = 26, + Skin13 = 27, + Skin14 = 28, + Skin15 = 29, + Skin16 = 30, + Skin17 = 31, + Skin18 = 32, + OrcFour = 33, } impl Skin { pub fn rgb(self) -> Rgb { let color = match self { - Self::Pale => (252, 211, 179), - Self::White => (253, 195, 164), - Self::Tanned => (222, 181, 151), - Self::Brown => (123, 80, 45), - Self::TannedBrown => (135, 70, 50), - Self::TannedDarkBrown => (116, 61, 43), + Self::Skin1 => (255, 229, 200), + Self::Skin2 => (255, 218, 190), + Self::Skin3 => (255, 206, 180), + Self::Skin4 => (255, 195, 170), + Self::Skin5 => (240, 184, 160), + Self::Skin6 => (225, 172, 150), + Self::Skin7 => (210, 161, 140), + Self::Skin8 => (195, 149, 130), + Self::Skin9 => (180, 138, 120), + Self::Skin10 => (165, 126, 110), + Self::Skin11 => (150, 114, 100), + Self::Skin12 => (135, 103, 90), + Self::Skin13 => (120, 92, 80), + Self::Skin14 => (105, 80, 70), + Self::Skin15 => (90, 69, 60), + Self::Skin16 => (75, 57, 50), + Self::Skin17 => (60, 46, 40), + Self::Skin18 => (45, 34, 30), Self::Iron => (135, 113, 95), Self::Steel => (108, 94, 86), Self::DanariOne => (104, 168, 196), @@ -605,6 +698,7 @@ impl Skin { Self::OrcOne => (61, 130, 42), Self::OrcTwo => (82, 117, 36), Self::OrcThree => (71, 94, 42), + Self::OrcFour => (97, 54, 29), Self::UndeadOne => (240, 243, 239), Self::UndeadTwo => (178, 178, 178), Self::UndeadThree => (145, 135, 121), @@ -614,12 +708,24 @@ impl Skin { pub fn light_rgb(self) -> Rgb { let color = match self { - Self::Pale => (255, 227, 193), - Self::White => (255, 210, 180), - Self::Tanned => (239, 197, 164), - Self::Brown => (150, 104, 68), - Self::TannedBrown => (148, 85, 64), - Self::TannedDarkBrown => (132, 74, 56), + Self::Skin1 => (255, 229, 200), + Self::Skin2 => (255, 218, 190), + Self::Skin3 => (255, 206, 180), + Self::Skin4 => (255, 195, 170), + Self::Skin5 => (240, 184, 160), + Self::Skin6 => (225, 172, 150), + Self::Skin7 => (210, 161, 140), + Self::Skin8 => (195, 149, 130), + Self::Skin9 => (180, 138, 120), + Self::Skin10 => (165, 126, 110), + Self::Skin11 => (150, 114, 100), + Self::Skin12 => (135, 103, 90), + Self::Skin13 => (120, 92, 80), + Self::Skin14 => (105, 80, 70), + Self::Skin15 => (90, 69, 60), + Self::Skin16 => (75, 57, 50), + Self::Skin17 => (60, 46, 40), + Self::Skin18 => (45, 34, 30), Self::Iron => (144, 125, 106), Self::Steel => (120, 107, 99), Self::DanariOne => (116, 176, 208), @@ -632,6 +738,7 @@ impl Skin { Self::OrcOne => (83, 165, 56), Self::OrcTwo => (92, 132, 46), Self::OrcThree => (84, 110, 54), + Self::OrcFour => (97, 54, 29), Self::UndeadOne => (254, 252, 251), Self::UndeadTwo => (190, 192, 191), Self::UndeadThree => (160, 151, 134), @@ -641,12 +748,24 @@ impl Skin { pub fn dark_rgb(self) -> Rgb { let color = match self { - Self::Pale => (229, 192, 163), - Self::White => (239, 179, 150), - Self::Tanned => (208, 167, 135), - Self::Brown => (106, 63, 30), - Self::TannedBrown => (122, 58, 40), - Self::TannedDarkBrown => (100, 47, 32), + Self::Skin1 => (242, 217, 189), + Self::Skin2 => (242, 207, 189), + Self::Skin3 => (242, 197, 172), + Self::Skin4 => (242, 186, 162), + Self::Skin5 => (212, 173, 150), + Self::Skin6 => (212, 163, 142), + Self::Skin7 => (196, 151, 132), + Self::Skin8 => (181, 139, 121), + Self::Skin9 => (168, 129, 113), + Self::Skin10 => (153, 117, 103), + Self::Skin11 => (138, 105, 92), + Self::Skin12 => (122, 93, 82), + Self::Skin13 => (107, 82, 72), + Self::Skin14 => (92, 70, 62), + Self::Skin15 => (77, 59, 51), + Self::Skin16 => (61, 47, 41), + Self::Skin17 => (48, 37, 32), + Self::Skin18 => (33, 25, 22), Self::Iron => (124, 99, 82), Self::Steel => (96, 81, 72), Self::DanariOne => (92, 155, 183), @@ -659,6 +778,7 @@ impl Skin { Self::OrcOne => (55, 114, 36), Self::OrcTwo => (70, 104, 29), Self::OrcThree => (60, 83, 32), + Self::OrcFour => (84, 47, 25), Self::UndeadOne => (229, 231, 230), Self::UndeadTwo => (165, 166, 164), Self::UndeadThree => (130, 122, 106), diff --git a/common/src/comp/body/quadruped_medium.rs b/common/src/comp/body/quadruped_medium.rs index 94c42ebe1e..cfcf0aafc6 100644 --- a/common/src/comp/body/quadruped_medium.rs +++ b/common/src/comp/body/quadruped_medium.rs @@ -10,11 +10,20 @@ impl Body { pub fn random() -> Self { let mut rng = thread_rng(); let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); - let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &species) + } + + #[inline] + pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self { + let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap(); Self { species, body_type } } } +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::QuadrupedMedium(body) } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Species { @@ -43,10 +52,11 @@ pub struct AllSpecies { pub tarasque: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { type Output = SpeciesMeta; - fn index(&self, index: Species) -> &Self::Output { + #[inline] + fn index(&self, &index: &'a Species) -> &Self::Output { match index { Species::Wolf => &self.wolf, Species::Saber => &self.saber, @@ -71,6 +81,14 @@ pub const ALL_SPECIES: [Species; 8] = [ Species::Tarasque, ]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/body/quadruped_small.rs b/common/src/comp/body/quadruped_small.rs index e3b86612d9..3b8dd5a26c 100644 --- a/common/src/comp/body/quadruped_small.rs +++ b/common/src/comp/body/quadruped_small.rs @@ -10,11 +10,20 @@ impl Body { pub fn random() -> Self { let mut rng = thread_rng(); let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); - let body_type = *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &species) + } + + #[inline] + pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self { + let body_type = *(&ALL_BODY_TYPES).choose(rng).unwrap(); Self { species, body_type } } } +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::QuadrupedSmall(body) } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Species { @@ -51,10 +60,11 @@ pub struct AllSpecies { pub holladon: SpeciesMeta, } -impl core::ops::Index for AllSpecies { +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { type Output = SpeciesMeta; - fn index(&self, index: Species) -> &Self::Output { + #[inline] + fn index(&self, &index: &'a Species) -> &Self::Output { match index { Species::Pig => &self.pig, Species::Fox => &self.fox, @@ -87,6 +97,14 @@ pub const ALL_SPECIES: [Species; 12] = [ Species::Holladon, ]; +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type Item = Species; + + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum BodyType { diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 5da492743d..a513f619cc 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -57,12 +57,12 @@ impl Input { /// Whether it's the first frame this input has been in /// its current state - pub fn is_just_pressed(&self) -> bool { (self.just_changed && self.is_pressed()) } + pub fn is_just_pressed(&self) -> bool { self.just_changed && self.is_pressed() } /// Whether input has been in current state longer than /// `DEFAULT_HOLD_DURATION` pub fn is_held_down(&self) -> bool { - (self.is_pressed() && self.duration >= DEFAULT_HOLD_DURATION) + self.is_pressed() && self.duration >= DEFAULT_HOLD_DURATION } /// Whether input has been pressed for longer than `threshold` diff --git a/common/src/comp/energy.rs b/common/src/comp/energy.rs index 0c74f20304..6a0c1853f8 100644 --- a/common/src/comp/energy.rs +++ b/common/src/comp/energy.rs @@ -12,11 +12,11 @@ pub struct Energy { #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum EnergySource { CastSpell, + Roll, + Climb, LevelUp, Regen, Revive, - Climb, - Roll, Unknown, } diff --git a/common/src/event.rs b/common/src/event.rs index 18c9dc7518..dc8e596050 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -8,22 +8,26 @@ use vek::*; pub struct SfxEventItem { pub sfx: SfxEvent, pub pos: Option>, + pub vol: Option, } impl SfxEventItem { - pub fn new(sfx: SfxEvent, pos: Option>) -> Self { Self { sfx, pos } } + pub fn new(sfx: SfxEvent, pos: Option>, vol: Option) -> Self { + Self { sfx, pos, vol } + } - pub fn at_player_position(sfx: SfxEvent) -> Self { Self { sfx, pos: None } } + pub fn at_player_position(sfx: SfxEvent) -> Self { + Self { + sfx, + pos: None, + vol: None, + } + } } #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] pub enum SfxEvent { Idle, - PlaceBlock, - RemoveBlock, - OpenChest, - ChatTellReceived, - OpenBag, Run, Roll, Climb, @@ -39,6 +43,8 @@ pub enum SfxEvent { ExtinguishLantern, Attack, AttackWolf, + Wield(comp::item::ToolKind), + Unwield(comp::item::ToolKind), } pub enum LocalEvent { diff --git a/common/src/lib.rs b/common/src/lib.rs index 68f9adfc21..70df9a2c1d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,12 @@ #![deny(unsafe_code)] #![type_length_limit = "1664759"] -#![feature(trait_alias, arbitrary_enum_discriminant, label_break_value)] +#![feature( + arbitrary_enum_discriminant, + bool_to_option, + label_break_value, + trait_alias, + type_alias_impl_trait +)] #[macro_use] extern crate serde_derive; #[macro_use] extern crate log; diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs index f117122c19..bae88d25f2 100644 --- a/common/src/net/post2.rs +++ b/common/src/net/post2.rs @@ -384,6 +384,7 @@ mod tests { } #[test] + #[ignore] fn send_recv_huge() { let (mut postoffice, sock) = create_postoffice::<(), Vec>(3).unwrap(); let test_msgs: Vec> = (0..5) diff --git a/common/src/npc.rs b/common/src/npc.rs index be378c9bc5..dd6f31dee9 100644 --- a/common/src/npc.rs +++ b/common/src/npc.rs @@ -1,4 +1,7 @@ -use crate::{assets, comp::AllBodies}; +use crate::{ + assets, + comp::{self, AllBodies, Body}, +}; use lazy_static::lazy_static; use rand::seq::SliceRandom; use std::{str::FromStr, sync::Arc}; @@ -41,6 +44,10 @@ pub struct BodyNames { /// NOTE: Deliberately don't (yet?) implement serialize. #[derive(Clone, Debug, Deserialize)] pub struct SpeciesNames { + /// The keyword used to refer to this species (e.g. via the command + /// console). Should be unique per species and distinct from all body + /// types (maybe in the future, it will just be unique per body type). + pub keyword: String, /// The generic name for NPCs of this species. pub generic: String, } @@ -56,11 +63,11 @@ impl FromStr for NpcKind { type Err = (); fn from_str(s: &str) -> Result { - let npc_names_json = &*NPC_NAMES; + let npc_names = &*NPC_NAMES; ALL_NPCS .iter() .copied() - .find(|&npc| npc_names_json[npc].keyword == s) + .find(|&npc| npc_names[npc].keyword == s) .ok_or(()) } } @@ -71,3 +78,129 @@ pub fn get_npc_name(npc_type: NpcKind) -> &'static str { // If no pretty name is found, fall back to the keyword. names.choose(&mut rand::thread_rng()).unwrap_or(keyword) } + +/// Randomly generates a body associated with this NPC kind. +pub fn kind_to_body(kind: NpcKind) -> Body { + match kind { + NpcKind::Humanoid => comp::humanoid::Body::random().into(), + NpcKind::Pig => comp::quadruped_small::Body::random().into(), + NpcKind::Wolf => comp::quadruped_medium::Body::random().into(), + NpcKind::Duck => comp::bird_medium::Body::random().into(), + NpcKind::Giant => comp::biped_large::Body::random().into(), + NpcKind::Rat => comp::critter::Body::random().into(), + } +} + +/// A combination of an NpcKind (representing an outer species to generate), and +/// a function that generates a fresh Body of a species that is part of that +/// NpcKind each time it's called. The reason things are done this way is that +/// when parsing spawn strings, we'd like to be able to randomize features that +/// haven't already been specified; for instance, if no species is specified we +/// should randomize species, while if a species is specified we can still +/// randomize other attributes like gender or clothing. +/// +/// TODO: Now that we return a closure, consider having the closure accept a +/// source of randomness explicitly, rather than always using ThreadRng. +pub struct NpcBody(pub NpcKind, pub Box Body>); + +impl FromStr for NpcBody { + type Err = (); + + /// Get an NPC kind from a string. If a body kind is matched without an + /// associated species, generate the species randmly within it; if an + /// explicit species is found, generate a random member of the species; + /// otherwise, return Err(()). + fn from_str(s: &str) -> Result { Self::from_str_with(s, kind_to_body) } +} + +impl NpcBody { + /// If there is an exact name match for a body kind, call kind_to_body on + /// it. Otherwise, if an explicit species is found, generate a random + /// member of the species; otherwise, return Err(()). + pub fn from_str_with(s: &str, kind_to_body: fn(NpcKind) -> Body) -> Result { + fn parse< + 'a, + B: Into + 'static, + // NOTE: Should be cheap in all cases, but if it weren't we should revamp the indexing + // method to take references instead of owned values. + Species: 'static, + BodyMeta, + SpeciesData: for<'b> core::ops::Index<&'b Species, Output = SpeciesNames>, + >( + s: &str, + npc_kind: NpcKind, + body_data: &'a comp::BodyData, + conv_func: for<'d> fn(&mut rand::rngs::ThreadRng, &'d Species) -> B, + ) -> Option + where + &'a SpeciesData: IntoIterator, + { + let npc_names = &body_data.species; + body_data + .species + .into_iter() + .find(|species| npc_names[species].keyword == s) + .map(|species| { + NpcBody( + npc_kind, + Box::new(move || conv_func(&mut rand::thread_rng(), &species).into()), + ) + }) + } + let npc_names = &NPC_NAMES; + // First, parse npc kind names. + NpcKind::from_str(s) + .map(|kind| NpcBody(kind, Box::new(move || kind_to_body(kind)))) + .ok() + // Otherwise, npc kind names aren't sufficient; we parse species names instead. + .or_else(|| { + parse( + s, + NpcKind::Humanoid, + &npc_names.humanoid, + comp::humanoid::Body::random_with, + ) + }) + .or_else(|| { + parse( + s, + NpcKind::Pig, + &npc_names.quadruped_small, + comp::quadruped_small::Body::random_with, + ) + }) + .or_else(|| { + parse( + s, + NpcKind::Wolf, + &npc_names.quadruped_medium, + comp::quadruped_medium::Body::random_with, + ) + }) + .or_else(|| { + parse( + s, + NpcKind::Duck, + &npc_names.bird_medium, + comp::bird_medium::Body::random_with, + ) + }) + .or_else(|| { + parse( + s, + NpcKind::Giant, + &npc_names.biped_large, + comp::biped_large::Body::random_with, + ) + }) + .or_else(|| { + parse( + s, + NpcKind::Rat, + &npc_names.critter, + comp::critter::Body::random_with, + ) + }) + .ok_or(()) + } +} diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index d6b8d7338f..861d2c2d07 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -46,7 +46,7 @@ pub fn handle_move_dir(ecs_data: &EcsStateData, update: &mut StateUpdate) { } pub fn handle_wield(ecs_data: &EcsStateData, update: &mut StateUpdate) { - if ecs_data.inputs.primary.is_pressed() || ecs_data.inputs.secondary.is_pressed() { + if ecs_data.inputs.primary.is_pressed() { if let Some(Tool(_)) = ecs_data.stats.equipment.main.as_ref().map(|i| &i.kind) { update.character = CharacterState::Wielding(None); } diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index 66ca102de2..b8dee638b2 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -13,6 +13,7 @@ use specs::{ }; const CHARGE_COST: i32 = 200; +const ROLL_COST: i32 = 30; pub struct Sys; diff --git a/common/src/sys/movement.rs b/common/src/sys/movement.rs index ac35e6ac8a..439ff9ceea 100644 --- a/common/src/sys/movement.rs +++ b/common/src/sys/movement.rs @@ -1,8 +1,8 @@ use super::phys::GRAVITY; use crate::{ comp::{ - ActionState, CharacterState, Controller, Mounting, MovementState::*, Ori, PhysicsState, - Pos, Stats, Vel, + ActionState, CharacterState, Controller, Energy, EnergySource, Mounting, MovementState::*, + Ori, PhysicsState, Pos, Stats, Vel, }, event::{EventBus, ServerEvent}, state::DeltaTime, @@ -31,6 +31,7 @@ const BLOCK_SPEED: f32 = 75.0; // Gravity is 9.81 * 4, so this makes gravity equal to .15 const GLIDE_ANTIGRAV: f32 = GRAVITY * 0.96; const CLIMB_SPEED: f32 = 5.0; +const CLIMB_COST: i32 = 5; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; @@ -54,6 +55,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, + WriteStorage<'a, Energy>, ReadStorage<'a, Uid>, ReadStorage<'a, Stats>, ReadStorage<'a, Controller>, @@ -72,6 +74,7 @@ impl<'a> System<'a> for Sys { mut positions, mut velocities, mut orientations, + mut energies, uids, stats, controllers, @@ -86,6 +89,7 @@ impl<'a> System<'a> for Sys { mut _pos, mut vel, mut ori, + mut energy, _uid, stats, controller, @@ -97,6 +101,7 @@ impl<'a> System<'a> for Sys { &mut positions, &mut velocities, &mut orientations, + &mut energies.restrict_mut(), &uids, &stats, &controllers, @@ -228,9 +233,21 @@ impl<'a> System<'a> for Sys { physics.on_wall, ) { if inputs.climb_down.is_pressed() && !inputs.climb.is_pressed() { - vel.0 -= dt.0 * vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0); + if energy + .get_mut_unchecked() + .try_change_by(-CLIMB_COST, EnergySource::Climb) + .is_ok() + { + vel.0 -= dt.0 * vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0); + } } else if inputs.climb.is_pressed() && !inputs.climb_down.is_pressed() { - vel.0.z = (vel.0.z + dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED).max(0.0); + if energy + .get_mut_unchecked() + .try_change_by(-CLIMB_COST, EnergySource::Climb) + .is_ok() + { + vel.0.z = (vel.0.z + dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED).max(0.0); + } } else { vel.0.z = (vel.0.z - dt.0 * GRAVITY * 0.01).min(CLIMB_SPEED); } diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index 548b636bda..20c1ce2dc4 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -91,7 +91,7 @@ impl<'a> System<'a> for Sys { (energy.regen_rate + ENERGY_REGEN_ACCEL * dt.0).min(100.0); } }, - // All other states do not regen and set the rate back to zero. + // Wield does not regen and sets the rate back to zero. CharacterState::Wielded(_) => { if energy.get_unchecked().regen_rate != 0.0 { energy.get_mut_unchecked().regen_rate = 0.0 diff --git a/rust-toolchain b/rust-toolchain index 238b1af439..47892367f9 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-01-18 +nightly-2020-02-06 diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b3b12a5ea3..1f46109481 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -8,7 +8,7 @@ use common::{ assets, comp, event::{EventBus, ServerEvent}, msg::{PlayerListUpdate, ServerMsg}, - npc::{get_npc_name, NpcKind}, + npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, terrain::TerrainChunkSize, @@ -234,11 +234,18 @@ lazy_static! { ), ChatCommand::new( "give_exp", - "{} {}", - "/give_exp : Give experience to specified player", + "{d} {}", + "/give_exp : Give experience to yourself or specify a target player", true, handle_exp, ), + ChatCommand::new( + "set_level", + "{d} {}", + "/set_level : Set own Level or specify a target player", + true, + handle_level + ), ChatCommand::new( "removelights", "{}", @@ -462,8 +469,8 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat } fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { - match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) { - (Some(opt_align), Some(id), opt_amount) => { + match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) { + (Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => { if let Some(alignment) = parse_alignment(entity, &opt_align) { let amount = opt_amount .and_then(|a| a.parse().ok()) @@ -487,7 +494,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C 10.0, ); - let body = kind_to_body(id); + let body = body(); let new_entity = server .state @@ -600,17 +607,6 @@ fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option } } -fn kind_to_body(kind: NpcKind) -> comp::Body { - match kind { - NpcKind::Humanoid => comp::Body::Humanoid(comp::humanoid::Body::random()), - NpcKind::Pig => comp::Body::QuadrupedSmall(comp::quadruped_small::Body::random()), - NpcKind::Wolf => comp::Body::QuadrupedMedium(comp::quadruped_medium::Body::random()), - NpcKind::Duck => comp::Body::BirdMedium(comp::bird_medium::Body::random()), - NpcKind::Giant => comp::Body::BipedLarge(comp::biped_large::Body::random()), - NpcKind::Rat => comp::Body::Critter(comp::critter::Body::random()), - } -} - fn handle_killnpcs(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { let ecs = server.state.ecs(); let mut stats = ecs.write_storage::(); @@ -1026,27 +1022,74 @@ spawn_rate {:?} "#, } } -fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { - let (a_alias, a_exp) = scan_fmt_some!(&args, action.arg_fmt, String, i64); - if let (Some(alias), Some(exp)) = (a_alias, a_exp) { - let ecs = server.state.ecs_mut(); - let opt_player = (&ecs.entities(), &ecs.read_storage::()) +fn find_target( + ecs: &specs::World, + opt_alias: Option, + fallback: EcsEntity, +) -> Result { + if let Some(alias) = opt_alias { + (&ecs.entities(), &ecs.read_storage::()) .join() .find(|(_, player)| player.alias == alias) - .map(|(entity, _)| entity); + .map(|(entity, _)| entity) + .ok_or(ServerMsg::private(format!("Player '{}' not found!", alias))) + } else { + Ok(fallback) + } +} + +fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let (a_exp, a_alias) = scan_fmt_some!(&args, action.arg_fmt, i64, String); + + if let Some(exp) = a_exp { + let ecs = server.state.ecs_mut(); + let target = find_target(&ecs, a_alias, entity); let mut error_msg = None; - match opt_player { - Some(_alias) => { - if let Some(stats) = ecs.write_storage::().get_mut(entity) { + match target { + Ok(player) => { + if let Some(stats) = ecs.write_storage::().get_mut(player) { stats.exp.change_by(exp); } else { error_msg = Some(ServerMsg::private(String::from("Player has no stats!"))); } }, - _ => { - error_msg = Some(ServerMsg::private(format!("Player '{}' not found!", alias))); + Err(e) => { + error_msg = Some(e); + }, + } + + if let Some(msg) = error_msg { + server.notify_client(entity, msg); + } + } +} + +fn handle_level(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let (a_lvl, a_alias) = scan_fmt_some!(&args, action.arg_fmt, u32, String); + + if let Some(lvl) = a_lvl { + let ecs = server.state.ecs_mut(); + let target = find_target(&ecs, a_alias, entity); + + let mut error_msg = None; + + match target { + Ok(player) => { + if let Some(stats) = ecs.write_storage::().get_mut(player) { + stats.level.set_level(lvl); + + stats.update_max_hp(); + stats + .health + .set_to(stats.health.maximum(), comp::HealthSource::LevelUp); + } else { + error_msg = Some(ServerMsg::private(String::from("Player has no stats!"))); + } + }, + Err(e) => { + error_msg = Some(e); }, } diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs new file mode 100644 index 0000000000..23bfa174db --- /dev/null +++ b/server/src/events/entity_creation.rs @@ -0,0 +1,84 @@ +use crate::{sys, Server, StateExt}; +use common::comp::{ + self, Agent, Alignment, Body, Gravity, LightEmitter, Pos, Projectile, Scale, Stats, Vel, + WaypointArea, +}; +use specs::{Builder, Entity as EcsEntity, WorldExt}; +use vek::{Rgb, Vec3}; + +pub fn handle_create_character( + server: &mut Server, + entity: EcsEntity, + name: String, + body: Body, + main: Option, +) { + let state = &mut server.state; + let server_settings = &server.server_settings; + + Server::create_player_character(state, entity, name, body, main, server_settings); + sys::subscription::initialize_region_subscription(state.ecs(), entity); +} + +pub fn handle_create_npc( + server: &mut Server, + pos: Pos, + stats: Stats, + body: Body, + agent: Agent, + alignment: Alignment, + scale: Scale, +) { + server + .state + .create_npc(pos, stats, body) + .with(agent) + .with(scale) + .with(alignment) + .build(); +} + +pub fn handle_shoot( + server: &mut Server, + entity: EcsEntity, + dir: Vec3, + body: Body, + light: Option, + projectile: Projectile, + gravity: Option, +) { + let state = server.state_mut(); + + let mut pos = state + .ecs() + .read_storage::() + .get(entity) + .expect("Failed to fetch entity") + .0; + + // TODO: Player height + pos.z += 1.2; + + let mut builder = + Server::create_projectile(state, Pos(pos), Vel(dir * 100.0), body, projectile); + if let Some(light) = light { + builder = builder.with(light) + } + if let Some(gravity) = gravity { + builder = builder.with(gravity) + } + + builder.build(); +} + +pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { + server + .create_object(Pos(pos), comp::object::Body::CampfireLit) + .with(LightEmitter { + offset: Vec3::unit_z() * 0.5, + col: Rgb::new(1.0, 0.65, 0.2), + strength: 2.0, + }) + .with(WaypointArea::default()) + .build(); +} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs new file mode 100644 index 0000000000..022c17ce74 --- /dev/null +++ b/server/src/events/entity_manipulation.rs @@ -0,0 +1,175 @@ +use crate::{client::Client, Server, SpawnPoint, StateExt}; +use common::{ + comp::{self, HealthChange, HealthSource, Player, Stats}, + msg::ServerMsg, + state::BlockChange, + sync::{Uid, WorldSyncExt}, + terrain::{Block, TerrainGrid}, + vol::{ReadVol, Vox}, +}; +use log::error; +use specs::{Entity as EcsEntity, WorldExt}; +use vek::Vec3; + +pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) { + let state = &server.state; + let ecs = state.ecs(); + if let Some(entity) = ecs.entity_from_uid(uid.into()) { + if let Some(stats) = ecs.write_storage::().get_mut(entity) { + stats.health.change_by(change); + } + } +} + +pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSource) { + let state = server.state_mut(); + + // Chat message + if let Some(player) = state.ecs().read_storage::().get(entity) { + let msg = if let HealthSource::Attack { by } = cause { + state.ecs().entity_from_uid(by.into()).and_then(|attacker| { + state + .ecs() + .read_storage::() + .get(attacker) + .map(|attacker_alias| { + format!("{} was killed by {}", &player.alias, &attacker_alias.alias) + }) + }) + } else { + None + } + .unwrap_or(format!("{} died", &player.alias)); + + state.notify_registered_clients(ServerMsg::kill(msg)); + } + + { + // Give EXP to the killer if entity had stats + let mut stats = state.ecs().write_storage::(); + if let Some(entity_stats) = stats.get(entity).cloned() { + if let HealthSource::Attack { by } = cause { + state.ecs().entity_from_uid(by.into()).map(|attacker| { + if let Some(attacker_stats) = stats.get_mut(attacker) { + // TODO: Discuss whether we should give EXP by Player + // Killing or not. + attacker_stats + .exp + .change_by((entity_stats.level.level() * 10) as i64); + } + }); + } + } + } + + if state + .ecs() + .write_storage::() + .get_mut(entity) + .is_some() + { + state + .ecs() + .write_storage() + .insert(entity, comp::Vel(Vec3::zero())) + .err() + .map(|err| error!("Failed to set zero vel on dead client: {:?}", err)); + state + .ecs() + .write_storage() + .insert(entity, comp::ForceUpdate) + .err() + .map(|err| error!("Failed to insert ForceUpdate on dead client: {:?}", err)); + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|energy| energy.set_to(energy.maximum(), comp::EnergySource::Revive)); + let _ = state + .ecs() + .write_storage::() + .insert(entity, comp::CharacterState::default()); + } else { + // If not a player delete the entity + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete destroyed entity: {:?}", err); + } + } +} + +pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) { + let state = &server.state; + if vel.z <= -37.0 { + if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { + let falldmg = (vel.z / 2.5) as i32; + if falldmg < 0 { + stats.health.change_by(comp::HealthChange { + amount: falldmg, + cause: comp::HealthSource::World, + }); + } + } + } +} + +pub fn handle_respawn(server: &Server, entity: EcsEntity) { + let state = &server.state; + + // Only clients can respawn + if state + .ecs() + .write_storage::() + .get_mut(entity) + .is_some() + { + let respawn_point = state + .read_component_cloned::(entity) + .map(|wp| wp.get_pos()) + .unwrap_or(state.ecs().read_resource::().0); + + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|stats| stats.revive()); + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|pos| pos.0 = respawn_point); + state + .ecs() + .write_storage() + .insert(entity, comp::ForceUpdate) + .err() + .map(|err| { + error!( + "Error inserting ForceUpdate component when respawning client: {:?}", + err + ) + }); + } +} + +pub fn handle_explosion(server: &Server, pos: Vec3, radius: f32) { + const RAYS: usize = 500; + + for _ in 0..RAYS { + let dir = Vec3::new( + rand::random::() - 0.5, + rand::random::() - 0.5, + rand::random::() - 0.5, + ) + .normalized(); + + let ecs = server.state.ecs(); + let mut block_change = ecs.write_resource::(); + + let _ = ecs + .read_resource::() + .ray(pos, pos + dir * radius) + .until(|_| rand::random::() < 0.05) + .for_each(|pos| block_change.set(pos, Block::empty())) + .cast(); + } +} diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs new file mode 100644 index 0000000000..96be66e52b --- /dev/null +++ b/server/src/events/interaction.rs @@ -0,0 +1,171 @@ +use crate::{ + client::{Client, RegionSubscription}, + Server, +}; +use common::{ + assets, comp, + msg::ServerMsg, + sync::{Uid, WorldSyncExt}, +}; +use log::error; +use specs::{world::WorldExt, Entity as EcsEntity}; + +pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) { + let state = server.state_mut(); + + if state + .ecs() + .read_storage::() + .get(mounter) + .is_none() + { + let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state + .ecs() + .read_storage::() + .get(mountee) + .cloned() + { + true + } else { + false + }; + + if not_mounting_yet { + if let (Some(mounter_uid), Some(mountee_uid)) = ( + state.ecs().uid_from_entity(mounter), + state.ecs().uid_from_entity(mountee), + ) { + state.write_component(mountee, comp::MountState::MountedBy(mounter_uid.into())); + state.write_component(mounter, comp::Mounting(mountee_uid.into())); + } + } + } +} + +pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) { + let state = server.state_mut(); + let mountee_entity = state + .ecs() + .write_storage::() + .get(mounter) + .and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into())); + if let Some(mountee_entity) = mountee_entity { + state + .ecs() + .write_storage::() + .get_mut(mountee_entity) + .map(|ms| *ms = comp::MountState::Unmounted); + } + state.delete_component::(mounter); +} + +pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { + let state = &server.state; + let ecs = state.ecs(); + if let (Some(possessor), Some(possesse)) = ( + ecs.entity_from_uid(possessor_uid.into()), + ecs.entity_from_uid(possesse_uid.into()), + ) { + // You can't possess other players + let mut clients = ecs.write_storage::(); + if clients.get_mut(possesse).is_none() { + if let Some(mut client) = clients.remove(possessor) { + client.notify(ServerMsg::SetPlayerEntity(possesse_uid.into())); + clients.insert(possesse, client).err().map(|e| { + error!( + "Error inserting client component during possession: {:?}", + e + ) + }); + // Create inventory if it doesn't exist + { + let mut inventories = ecs.write_storage::(); + if let Some(inventory) = inventories.get_mut(possesse) { + inventory.push(assets::load_expect_cloned("common.items.debug.possess")); + } else { + inventories + .insert(possesse, comp::Inventory { + slots: vec![ + Some(assets::load_expect_cloned("common.items.debug.possess")), + None, + None, + None, + None, + None, + None, + None, + ], + }) + .err() + .map(|e| { + error!( + "Error inserting inventory component during possession: {:?}", + e + ) + }); + } + } + ecs.write_storage::() + .insert(possesse, comp::InventoryUpdate) + .err() + .map(|e| { + error!( + "Error inserting inventory update component during possession: {:?}", + e + ) + }); + // Move player component + { + let mut players = ecs.write_storage::(); + if let Some(player) = players.remove(possessor) { + players.insert(possesse, player).err().map(|e| { + error!( + "Error inserting player component during possession: {:?}", + e + ) + }); + } + } + // Transfer region subscription + { + let mut subscriptions = ecs.write_storage::(); + if let Some(s) = subscriptions.remove(possessor) { + subscriptions.insert(possesse, s).err().map(|e| { + error!( + "Error inserting subscription component during possession: {:?}", + e + ) + }); + } + } + // Remove will of the entity + ecs.write_storage::().remove(possesse); + // Reset controller of former shell + ecs.write_storage::() + .get_mut(possessor) + .map(|c| c.reset()); + // Transfer admin powers + { + let mut admins = ecs.write_storage::(); + if let Some(admin) = admins.remove(possessor) { + admins.insert(possesse, admin).err().map(|e| { + error!("Error inserting admin component during possession: {:?}", e) + }); + } + } + // Transfer waypoint + { + let mut waypoints = ecs.write_storage::(); + if let Some(waypoint) = waypoints.remove(possessor) { + waypoints.insert(possesse, waypoint).err().map(|e| { + error!( + "Error inserting waypoint component during possession {:?}", + e + ) + }); + } + } + } + } + } +} diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs new file mode 100644 index 0000000000..4485a62fb7 --- /dev/null +++ b/server/src/events/inventory_manip.rs @@ -0,0 +1,221 @@ +use crate::{Server, StateExt}; +use common::{ + comp, + sync::WorldSyncExt, + terrain::block::Block, + vol::{ReadVol, Vox}, +}; +use log::error; +use rand::Rng; +use specs::{join::Join, world::WorldExt, Builder, Entity as EcsEntity}; +use vek::Vec3; + +pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) { + let state = server.state_mut(); + let mut dropped_items = Vec::new(); + + match manip { + comp::InventoryManip::Pickup(uid) => { + // TODO: enforce max pickup range + let item_entity = if let (Some((item, item_entity)), Some(inv)) = ( + state + .ecs() + .entity_from_uid(uid.into()) + .and_then(|item_entity| { + state + .ecs() + .write_storage::() + .get_mut(item_entity) + .map(|item| (item.clone(), item_entity)) + }), + state + .ecs() + .write_storage::() + .get_mut(entity), + ) { + if inv.push(item).is_none() { + Some(item_entity) + } else { + None + } + } else { + None + }; + + if let Some(item_entity) = item_entity { + if let Err(err) = state.delete_entity_recorded(item_entity) { + error!("Failed to delete picked up item entity: {:?}", err); + } + } + + state.write_component(entity, comp::InventoryUpdate); + }, + + comp::InventoryManip::Collect(pos) => { + let block = state.terrain().get(pos).ok().copied(); + if let Some(block) = block { + if block.is_collectible() + && state + .ecs() + .read_storage::() + .get(entity) + .map(|inv| !inv.is_full()) + .unwrap_or(false) + && state.try_set_block(pos, Block::empty()).is_some() + { + comp::Item::try_reclaim_from_block(block) + .map(|item| state.give_item(entity, item)); + } + } + }, + + comp::InventoryManip::Use(slot) => { + let item_opt = state + .ecs() + .write_storage::() + .get_mut(entity) + .and_then(|inv| inv.remove(slot)); + + if let Some(item) = item_opt { + match item.kind { + comp::ItemKind::Tool { .. } => { + if let Some(stats) = + state.ecs().write_storage::().get_mut(entity) + { + // Insert old item into inventory + if let Some(old_item) = stats.equipment.main.take() { + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, old_item)); + } + + stats.equipment.main = Some(item); + } + }, + comp::ItemKind::Consumable { effect, .. } => { + state.apply_effect(entity, effect); + }, + comp::ItemKind::Utility { kind } => match kind { + comp::item::Utility::Collar => { + let reinsert = if let Some(pos) = + state.read_storage::().get(entity) + { + if ( + &state.read_storage::(), + &state.read_storage::(), + ) + .join() + .filter(|(alignment, _)| { + alignment == &&comp::Alignment::Owned(entity) + }) + .count() + >= 3 + { + true + } else if let Some(tameable_entity) = { + let nearest_tameable = ( + &state.ecs().entities(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + ) + .join() + .filter(|(_, wild_pos, _)| { + wild_pos.0.distance_squared(pos.0) < 5.0f32.powf(2.0) + }) + .filter(|(_, _, alignment)| { + alignment == &&comp::Alignment::Wild + }) + .min_by_key(|(_, wild_pos, _)| { + (wild_pos.0.distance_squared(pos.0) * 100.0) as i32 + }) + .map(|(entity, _, _)| entity); + nearest_tameable + } { + let _ = state + .ecs() + .write_storage() + .insert(tameable_entity, comp::Alignment::Owned(entity)); + let _ = state + .ecs() + .write_storage() + .insert(tameable_entity, comp::Agent::default()); + false + } else { + true + } + } else { + true + }; + + if reinsert { + let _ = state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, item)); + } + }, + }, + _ => { + let _ = state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, item)); + }, + } + } + + state.write_component(entity, comp::InventoryUpdate); + }, + + comp::InventoryManip::Swap(a, b) => { + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.swap_slots(a, b)); + state.write_component(entity, comp::InventoryUpdate); + }, + + comp::InventoryManip::Drop(slot) => { + let item = state + .ecs() + .write_storage::() + .get_mut(entity) + .and_then(|inv| inv.remove(slot)); + + if let (Some(item), Some(pos)) = + (item, state.ecs().read_storage::().get(entity)) + { + dropped_items.push(( + *pos, + state + .ecs() + .read_storage::() + .get(entity) + .copied() + .unwrap_or(comp::Ori(Vec3::unit_y())), + item, + )); + } + state.write_component(entity, comp::InventoryUpdate); + }, + } + + // Drop items + for (pos, ori, item) in dropped_items { + let vel = ori.0.normalized() * 5.0 + + Vec3::unit_z() * 10.0 + + Vec3::::zero().map(|_| rand::thread_rng().gen::() - 0.5) * 4.0; + + server + .create_object(Default::default(), comp::object::Body::Pouch) + .with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25)) + .with(item) + .with(comp::Vel(vel)) + .build(); + } +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs new file mode 100644 index 0000000000..dd9f3b332c --- /dev/null +++ b/server/src/events/mod.rs @@ -0,0 +1,109 @@ +use crate::Server; +use common::event::{EventBus, ServerEvent}; +use entity_creation::{ + handle_create_character, handle_create_npc, handle_create_waypoint, handle_shoot, +}; +use entity_manipulation::{ + handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_respawn, +}; +use interaction::{handle_mount, handle_possess, handle_unmount}; +use inventory_manip::handle_inventory; +use player::{handle_client_disconnect, handle_exit_ingame}; +use specs::{Entity as EcsEntity, WorldExt}; + +mod entity_creation; +mod entity_manipulation; +mod interaction; +mod inventory_manip; +mod player; + +pub enum Event { + ClientConnected { + entity: EcsEntity, + }, + ClientDisconnected { + entity: EcsEntity, + }, + Chat { + entity: Option, + msg: String, + }, +} + +impl Server { + pub fn handle_events(&mut self) -> Vec { + let mut frontend_events = Vec::new(); + + let mut requested_chunks = Vec::new(); + let mut chat_commands = Vec::new(); + + let events = self + .state + .ecs() + .read_resource::>() + .recv_all(); + + for event in events { + match event { + ServerEvent::Explosion { pos, radius } => handle_explosion(&self, pos, radius), + ServerEvent::Shoot { + entity, + dir, + body, + light, + projectile, + gravity, + } => handle_shoot(self, entity, dir, body, light, projectile, gravity), + ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change), + ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause), + ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip), + ServerEvent::Respawn(entity) => handle_respawn(&self, entity), + ServerEvent::LandOnGround { entity, vel } => { + handle_land_on_ground(&self, entity, vel) + }, + ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee), + ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), + ServerEvent::Possess(possessor_uid, possesse_uid) => { + handle_possess(&self, possessor_uid, possesse_uid) + }, + ServerEvent::CreateCharacter { + entity, + name, + body, + main, + } => handle_create_character(self, entity, name, body, main), + ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity), + ServerEvent::CreateNpc { + pos, + stats, + body, + agent, + alignment, + scale, + } => handle_create_npc(self, pos, stats, body, agent, alignment, scale), + ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos), + ServerEvent::ClientDisconnect(entity) => { + frontend_events.push(handle_client_disconnect(self, entity)) + }, + + ServerEvent::ChunkRequest(entity, key) => { + requested_chunks.push((entity, key)); + }, + ServerEvent::ChatCmd(entity, cmd) => { + chat_commands.push((entity, cmd)); + }, + } + } + + // Generate requested chunks. + for (entity, key) in requested_chunks { + self.generate_chunk(entity, key); + } + + for (entity, cmd) in chat_commands { + self.process_chat_cmd(entity, cmd); + } + + frontend_events + } +} diff --git a/server/src/events/player.rs b/server/src/events/player.rs new file mode 100644 index 0000000000..33ea7e7b63 --- /dev/null +++ b/server/src/events/player.rs @@ -0,0 +1,60 @@ +use super::Event; +use crate::{client::Client, Server, StateExt}; +use common::{ + comp, + msg::{ClientState, PlayerListUpdate, ServerMsg}, + sync::{Uid, UidAllocator}, +}; +use log::error; +use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, WorldExt}; + +pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { + let state = server.state_mut(); + + // Create new entity with just `Client`, `Uid`, and `Player` components + // Easier than checking and removing all other known components + // Note: If other `ServerEvent`s are referring to this entity they will be + // disrupted + let maybe_client = state.ecs().write_storage::().remove(entity); + let maybe_uid = state.read_component_cloned::(entity); + let maybe_player = state.ecs().write_storage::().remove(entity); + if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) { + // Tell client its request was successful + client.allow_state(ClientState::Registered); + // Tell client to clear out other entities and its own components + client.notify(ServerMsg::ExitIngameCleanup); + + let entity_builder = state.ecs_mut().create_entity().with(client).with(player); + // Ensure UidAllocator maps this uid to the new entity + let uid = entity_builder + .world + .write_resource::() + .allocate(entity_builder.entity, Some(uid.into())); + entity_builder.with(uid).build(); + } + // Delete old entity + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete entity when removing character: {:?}", err); + } +} + +pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event { + let state = server.state_mut(); + + // Tell other clients to remove from player list + if let (Some(uid), Some(_)) = ( + state.read_storage::().get(entity), + state.read_storage::().get(entity), + ) { + state.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove( + (*uid).into(), + ))) + } + + // Delete client entity + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete disconnected client: {:?}", err); + } + + Event::ClientDisconnected { entity } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 7c0a338b67..089713a679 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -6,6 +6,7 @@ pub mod chunk_generator; pub mod client; pub mod cmd; pub mod error; +pub mod events; pub mod input; pub mod metrics; pub mod settings; @@ -13,7 +14,7 @@ pub mod sys; #[cfg(not(feature = "worldgen"))] mod test_world; // Reexports -pub use crate::{error::Error, input::Input, settings::ServerSettings}; +pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings}; use crate::{ auth_provider::AuthProvider, @@ -26,19 +27,18 @@ use common::{ assets, comp, effect::Effect, event::{EventBus, ServerEvent}, - msg::{ClientMsg, ClientState, PlayerListUpdate, ServerError, ServerInfo, ServerMsg}, + msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg}, net::PostOffice, - state::{BlockChange, State, TimeOfDay}, - sync::{Uid, UidAllocator, WorldSyncExt}, - terrain::{block::Block, TerrainChunkSize, TerrainGrid}, - vol::{ReadVol, RectVolSize, Vox}, + state::{State, TimeOfDay}, + sync::{Uid, WorldSyncExt}, + terrain::TerrainChunkSize, + vol::{ReadVol, RectVolSize}, }; use log::{debug, error, warn}; use metrics::ServerMetrics; -use rand::Rng; use specs::{ - join::Join, saveload::MarkerAllocator, world::EntityBuilder as EcsEntityBuilder, Builder, - Entity as EcsEntity, RunNow, SystemData, WorldExt, + join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow, + SystemData, WorldExt, }; use std::{ i32, @@ -57,19 +57,6 @@ use world::{ const CLIENT_TIMEOUT: f64 = 20.0; // Seconds -pub enum Event { - ClientConnected { - entity: EcsEntity, - }, - ClientDisconnected { - entity: EcsEntity, - }, - Chat { - entity: Option, - msg: String, - }, -} - #[derive(Copy, Clone)] struct SpawnPoint(Vec3); @@ -318,661 +305,6 @@ impl Server { } /// Handle events coming through via the event bus - fn handle_events(&mut self) -> Vec { - let mut frontend_events = Vec::new(); - - let mut requested_chunks = Vec::new(); - let mut dropped_items = Vec::new(); - let mut chat_commands = Vec::new(); - - let events = self - .state - .ecs() - .read_resource::>() - .recv_all(); - for event in events { - let state = &mut self.state; - - let server_settings = &self.server_settings; - - match event { - ServerEvent::Explosion { pos, radius } => { - const RAYS: usize = 500; - - for _ in 0..RAYS { - let dir = Vec3::new( - rand::random::() - 0.5, - rand::random::() - 0.5, - rand::random::() - 0.5, - ) - .normalized(); - - let ecs = state.ecs(); - let mut block_change = ecs.write_resource::(); - - let _ = ecs - .read_resource::() - .ray(pos, pos + dir * radius) - .until(|_| rand::random::() < 0.05) - .for_each(|pos| block_change.set(pos, Block::empty())) - .cast(); - } - }, - - ServerEvent::Shoot { - entity, - dir, - body, - light, - projectile, - gravity, - } => { - let mut pos = state - .ecs() - .read_storage::() - .get(entity) - .expect("Failed to fetch entity") - .0; - - // TODO: Player height - pos.z += 1.2; - - let mut builder = Self::create_projectile( - state, - comp::Pos(pos), - comp::Vel(dir * 100.0), - body, - projectile, - ); - if let Some(light) = light { - builder = builder.with(light) - } - if let Some(gravity) = gravity { - builder = builder.with(gravity) - } - - builder.build(); - }, - - ServerEvent::Damage { uid, change } => { - let ecs = state.ecs(); - if let Some(entity) = ecs.entity_from_uid(uid.into()) { - if let Some(stats) = ecs.write_storage::().get_mut(entity) { - stats.health.change_by(change); - } - } - }, - - ServerEvent::Destroy { entity, cause } => { - // Chat message - if let Some(player) = state.ecs().read_storage::().get(entity) { - let msg = if let comp::HealthSource::Attack { by } = cause { - state.ecs().entity_from_uid(by.into()).and_then(|attacker| { - state - .ecs() - .read_storage::() - .get(attacker) - .map(|attacker_alias| { - format!( - "{} was killed by {}", - &player.alias, &attacker_alias.alias - ) - }) - }) - } else { - None - } - .unwrap_or(format!("{} died", &player.alias)); - - state.notify_registered_clients(ServerMsg::kill(msg)); - } - - { - // Give EXP to the killer if entity had stats - let mut stats = state.ecs().write_storage::(); - if let Some(entity_stats) = stats.get(entity).cloned() { - if let comp::HealthSource::Attack { by } = cause { - state.ecs().entity_from_uid(by.into()).map(|attacker| { - if let Some(attacker_stats) = stats.get_mut(attacker) { - // TODO: Discuss whether we should give EXP by Player - // Killing or not. - attacker_stats - .exp - .change_by((entity_stats.level.level() * 10) as i64); - } - }); - } - } - } - - if state - .ecs() - .write_storage::() - .get_mut(entity) - .is_some() - { - state - .ecs() - .write_storage() - .insert(entity, comp::Vel(Vec3::zero())) - .err() - .map(|err| error!("Failed to set zero vel on dead client: {:?}", err)); - state - .ecs() - .write_storage() - .insert(entity, comp::ForceUpdate) - .err() - .map(|err| { - error!("Failed to insert ForceUpdate on dead client: {:?}", err) - }); - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|energy| { - energy.set_to(energy.maximum(), comp::EnergySource::Revive) - }); - let _ = state - .ecs() - .write_storage::() - .insert(entity, comp::CharacterState::default()); - } else { - // If not a player delete the entity - if let Err(err) = state.delete_entity_recorded(entity) { - error!("Failed to delete destroyed entity: {:?}", err); - } - } - }, - - ServerEvent::InventoryManip(entity, manip) => { - match manip { - comp::InventoryManip::Pickup(uid) => { - // TODO: enforce max pickup range - let item_entity = if let (Some((item, item_entity)), Some(inv)) = ( - state - .ecs() - .entity_from_uid(uid.into()) - .and_then(|item_entity| { - state - .ecs() - .write_storage::() - .get_mut(item_entity) - .map(|item| (item.clone(), item_entity)) - }), - state - .ecs() - .write_storage::() - .get_mut(entity), - ) { - if inv.push(item).is_none() { - Some(item_entity) - } else { - None - } - } else { - None - }; - - if let Some(item_entity) = item_entity { - if let Err(err) = state.delete_entity_recorded(item_entity) { - error!("Failed to delete picked up item entity: {:?}", err); - } - } - - state.write_component(entity, comp::InventoryUpdate); - }, - - comp::InventoryManip::Collect(pos) => { - let block = state.terrain().get(pos).ok().copied(); - if let Some(block) = block { - if block.is_collectible() - && state - .ecs() - .read_storage::() - .get(entity) - .map(|inv| !inv.is_full()) - .unwrap_or(false) - && state.try_set_block(pos, Block::empty()).is_some() - { - comp::Item::try_reclaim_from_block(block) - .map(|item| state.give_item(entity, item)); - } - } - }, - - comp::InventoryManip::Use(slot) => { - let item_opt = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.remove(slot)); - - if let Some(item) = item_opt { - match item.kind { - comp::ItemKind::Tool { .. } => { - if let Some(stats) = state - .ecs() - .write_storage::() - .get_mut(entity) - { - // Insert old item into inventory - if let Some(old_item) = stats.equipment.main.take() { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, old_item)); - } - - stats.equipment.main = Some(item); - } - }, - comp::ItemKind::Consumable { effect, .. } => { - state.apply_effect(entity, effect); - }, - comp::ItemKind::Utility { kind } => match kind { - comp::item::Utility::Collar => { - let reinsert = if let Some(pos) = - state.read_storage::().get(entity) - { - if ( - &state.read_storage::(), - &state.read_storage::(), - ) - .join() - .filter(|(alignment, _)| { - alignment - == &&comp::Alignment::Owned(entity) - }) - .count() - >= 3 - { - true - } else if let Some(tameable_entity) = { - let nearest_tameable = ( - &state.ecs().entities(), - &state.ecs().read_storage::(), - &state - .ecs() - .read_storage::(), - ) - .join() - .filter(|(_, wild_pos, _)| { - wild_pos.0.distance_squared(pos.0) - < 5.0f32.powf(2.0) - }) - .filter(|(_, _, alignment)| { - alignment == &&comp::Alignment::Wild - }) - .min_by_key(|(_, wild_pos, _)| { - (wild_pos.0.distance_squared(pos.0) - * 100.0) - as i32 - }) - .map(|(entity, _, _)| entity); - nearest_tameable - } { - let _ = state.ecs().write_storage().insert( - tameable_entity, - comp::Alignment::Owned(entity), - ); - let _ = state.ecs().write_storage().insert( - tameable_entity, - comp::Agent::default(), - ); - false - } else { - true - } - } else { - true - }; - - if reinsert { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, item)); - } - }, - }, - _ => { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, item)); - }, - } - } - - state.write_component(entity, comp::InventoryUpdate); - }, - - comp::InventoryManip::Swap(a, b) => { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.swap_slots(a, b)); - state.write_component(entity, comp::InventoryUpdate); - }, - - comp::InventoryManip::Drop(slot) => { - let item = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.remove(slot)); - - if let (Some(item), Some(pos)) = - (item, state.ecs().read_storage::().get(entity)) - { - dropped_items.push(( - *pos, - state - .ecs() - .read_storage::() - .get(entity) - .copied() - .unwrap_or(comp::Ori(Vec3::unit_y())), - item, - )); - } - state.write_component(entity, comp::InventoryUpdate); - }, - } - }, - - ServerEvent::Respawn(entity) => { - // Only clients can respawn - if state - .ecs() - .write_storage::() - .get_mut(entity) - .is_some() - { - let respawn_point = state - .read_component_cloned::(entity) - .map(|wp| wp.get_pos()) - .unwrap_or(state.ecs().read_resource::().0); - - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|stats| stats.revive()); - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|pos| pos.0 = respawn_point); - state - .ecs() - .write_storage() - .insert(entity, comp::ForceUpdate) - .err() - .map(|err| { - error!( - "Error inserting ForceUpdate component when respawning \ - client: {:?}", - err - ) - }); - } - }, - - ServerEvent::LandOnGround { entity, vel } => { - if vel.z <= -37.0 { - if let Some(stats) = - state.ecs().write_storage::().get_mut(entity) - { - let falldmg = (vel.z / 2.5) as i32; - if falldmg < 0 { - stats.health.change_by(comp::HealthChange { - amount: falldmg, - cause: comp::HealthSource::World, - }); - } - } - } - }, - - ServerEvent::Mount(mounter, mountee) => { - if state - .ecs() - .read_storage::() - .get(mounter) - .is_none() - { - let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state - .ecs() - .read_storage::() - .get(mountee) - .cloned() - { - true - } else { - false - }; - - if not_mounting_yet { - if let (Some(mounter_uid), Some(mountee_uid)) = ( - state.ecs().uid_from_entity(mounter), - state.ecs().uid_from_entity(mountee), - ) { - state.write_component( - mountee, - comp::MountState::MountedBy(mounter_uid.into()), - ); - state.write_component(mounter, comp::Mounting(mountee_uid.into())); - } - } - } - }, - - ServerEvent::Unmount(mounter) => { - let mountee_entity = state - .ecs() - .write_storage::() - .get(mounter) - .and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into())); - if let Some(mountee_entity) = mountee_entity { - state - .ecs() - .write_storage::() - .get_mut(mountee_entity) - .map(|ms| *ms = comp::MountState::Unmounted); - } - state.delete_component::(mounter); - }, - - ServerEvent::Possess(possessor_uid, possesse_uid) => { - let ecs = state.ecs(); - if let (Some(possessor), Some(possesse)) = ( - ecs.entity_from_uid(possessor_uid.into()), - ecs.entity_from_uid(possesse_uid.into()), - ) { - // You can't possess other players - let mut clients = ecs.write_storage::(); - if clients.get_mut(possesse).is_none() { - if let Some(mut client) = clients.remove(possessor) { - client.notify(ServerMsg::SetPlayerEntity(possesse_uid.into())); - let _ = clients.insert(possesse, client); - // Create inventory if it doesn't exist - { - let mut inventories = ecs.write_storage::(); - if let Some(inventory) = inventories.get_mut(possesse) { - inventory.push(assets::load_expect_cloned( - "common.items.debug.possess", - )); - } else { - let _ = inventories.insert(possesse, comp::Inventory { - slots: vec![Some(assets::load_expect_cloned( - "common.items.debug.possess", - ))], - }); - } - } - let _ = ecs - .write_storage::() - .insert(possesse, comp::InventoryUpdate); - // Move player component - { - let mut players = ecs.write_storage::(); - if let Some(player) = players.remove(possessor) { - let _ = players.insert(possesse, player); - } - } - // Remove will of the entity - let _ = ecs.write_storage::().remove(possesse); - // Transfer admin powers - { - let mut admins = ecs.write_storage::(); - if let Some(admin) = admins.remove(possessor) { - let _ = admins.insert(possesse, admin); - } - } - // Transfer waypoint - { - let mut waypoints = ecs.write_storage::(); - if let Some(waypoint) = waypoints.remove(possessor) { - let _ = waypoints.insert(possesse, waypoint); - } - } - } - } - } - }, - - ServerEvent::CreateCharacter { - entity, - name, - body, - main, - } => { - Self::create_player_character( - state, - entity, - name, - body, - main, - &server_settings, - ); - sys::subscription::initialize_region_subscription(state.ecs(), entity); - }, - - ServerEvent::ExitIngame { entity } => { - // Create new entity with just `Client`, `Uid`, and `Player` components - // Easier than checking and removing all other known components - // Note: If other `ServerEvent`s are referring to this entity they will be - // disrupted - let maybe_client = state.ecs().write_storage::().remove(entity); - let maybe_uid = state.read_component_cloned::(entity); - let maybe_player = state.ecs().write_storage::().remove(entity); - if let (Some(mut client), Some(uid), Some(player)) = - (maybe_client, maybe_uid, maybe_player) - { - // Tell client its request was successful - client.allow_state(ClientState::Registered); - // Tell client to clear out other entities and its own components - client.notify(ServerMsg::ExitIngameCleanup); - - let entity_builder = - state.ecs_mut().create_entity().with(client).with(player); - // Ensure UidAllocator maps this uid to the new entity - let uid = entity_builder - .world - .write_resource::() - .allocate(entity_builder.entity, Some(uid.into())); - entity_builder.with(uid).build(); - } - // Delete old entity - if let Err(err) = state.delete_entity_recorded(entity) { - error!("Failed to delete entity when removing character: {:?}", err); - } - }, - - ServerEvent::CreateNpc { - pos, - stats, - body, - agent, - alignment, - scale, - } => { - state - .create_npc(pos, stats, body) - .with(agent) - .with(scale) - .with(alignment) - .build(); - }, - - ServerEvent::CreateWaypoint(pos) => { - self.create_object(comp::Pos(pos), comp::object::Body::CampfireLit) - .with(comp::LightEmitter { - offset: Vec3::unit_z() * 0.5, - col: Rgb::new(1.0, 0.65, 0.2), - strength: 2.0, - }) - .with(comp::WaypointArea::default()) - .build(); - }, - - ServerEvent::ClientDisconnect(entity) => { - // Tell other clients to remove from player list - if let (Some(uid), Some(_)) = ( - state.read_storage::().get(entity), - state.read_storage::().get(entity), - ) { - state.notify_registered_clients(ServerMsg::PlayerListUpdate( - PlayerListUpdate::Remove((*uid).into()), - )) - } - - // Delete client entity - if let Err(err) = state.delete_entity_recorded(entity) { - error!("Failed to delete disconnected client: {:?}", err); - } - - frontend_events.push(Event::ClientDisconnected { entity }); - }, - - ServerEvent::ChunkRequest(entity, key) => { - requested_chunks.push((entity, key)); - }, - - ServerEvent::ChatCmd(entity, cmd) => { - chat_commands.push((entity, cmd)); - }, - } - } - - // Generate requested chunks. - for (entity, key) in requested_chunks { - self.generate_chunk(entity, key); - } - - // Drop items - for (pos, ori, item) in dropped_items { - let vel = ori.0.normalized() * 5.0 - + Vec3::unit_z() * 10.0 - + Vec3::::zero().map(|_| rand::thread_rng().gen::() - 0.5) * 4.0; - self.create_object(Default::default(), comp::object::Body::Pouch) - .with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25)) - .with(item) - .with(comp::Vel(vel)) - .build(); - } - - for (entity, cmd) in chat_commands { - self.process_chat_cmd(entity, cmd); - } - - frontend_events - } /// Execute a single server tick, handle input and update the game state by /// the given duration. diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 0d5b7948fc..2ca9c72912 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -103,13 +103,14 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos)); } else { fn get_npc_name< + 'a, Species, - SpeciesData: core::ops::Index, + SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>, >( - body_data: &comp::BodyData, + body_data: &'a comp::BodyData, species: Species, - ) -> &str { - &body_data.species[species].generic + ) -> &'a str { + &body_data.species[&species].generic } const SPAWN_NPCS: &'static [fn() -> ( String, diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index db5a64754c..7c3e6debb0 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -55,8 +55,7 @@ num = "0.2.0" backtrace = "0.3.40" rand = "0.7.2" treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" } -# context for pinning to commit: https://gitlab.com/veloren/veloren/issues/280 -rodio = { git = "https://github.com/RustAudio/rodio", rev = "e5474a2"} +rodio = { version = "0.10", default-features = false, features = ["wav", "vorbis"] } cpal = "0.10" crossbeam = "=0.7.2" hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] } @@ -73,6 +72,7 @@ winres = "0.1" [dev-dependencies] criterion = "0.3" +git2 = "0.10" world = { package = "veloren-world", path = "../world" } [[bench]] diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs index 00c36646c0..8c65e6dc98 100644 --- a/voxygen/src/audio/channel.rs +++ b/voxygen/src/audio/channel.rs @@ -1,51 +1,125 @@ -use crate::audio::fader::Fader; -use rodio::{Device, Sample, Source, SpatialSink}; +use crate::audio::fader::{FadeDirection, Fader}; +use rodio::{Device, Sample, Sink, Source, SpatialSink}; use vek::*; -#[derive(PartialEq, Clone, Copy)] -pub enum AudioType { - Sfx, - Music, - None, -} - #[derive(PartialEq, Clone, Copy)] enum ChannelState { - // Init, - // ToPlay, - // Loading, Playing, - Stopping, + Fading, Stopped, } +/// Each MusicChannel has a MusicChannelTag which help us determine how we +/// should transition between music types #[derive(PartialEq, Clone, Copy)] -pub enum ChannelTag { +pub enum MusicChannelTag { TitleMusic, - Soundtrack, + Exploration, } -pub struct Channel { - id: usize, - sink: SpatialSink, - audio_type: AudioType, +/// A MusicChannel uses a non-positional audio sink designed to play music which +/// is always heard at the player's position +pub struct MusicChannel { + tag: MusicChannelTag, + sink: Sink, state: ChannelState, fader: Fader, - tag: Option, +} + +impl MusicChannel { + pub fn new(device: &Device) -> Self { + Self { + sink: Sink::new(device), + tag: MusicChannelTag::TitleMusic, + state: ChannelState::Stopped, + fader: Fader::default(), + } + } + + // Play a music track item on this channel. If the channel has an existing track + // playing, the new sounds will be appended and played once they complete. + // Otherwise it will begin playing immediately. + pub fn play(&mut self, source: S, tag: MusicChannelTag) + where + S: Source + Send + 'static, + S::Item: Sample, + S::Item: Send, + ::Item: std::fmt::Debug, + { + self.tag = tag; + self.sink.append(source); + + self.state = if !self.fader.is_finished() { + ChannelState::Fading + } else { + ChannelState::Playing + }; + } + + /// Set the volume of the current channel. If the channel is currently + /// fading, the volume of the fader is updated to this value. + pub fn set_volume(&mut self, volume: f32) { + if !self.fader.is_finished() { + self.fader.update_target_volume(volume); + } else { + self.sink.set_volume(volume); + } + } + + /// Set a fader for the channel. If a fader exists already, it is replaced. + /// If the channel has not begun playing, and the fader is set to fade in, + /// we set the volume of the channel to the initial volume of the fader so + /// that the volumes match when playing begins. + pub fn set_fader(&mut self, fader: Fader) { + self.fader = fader; + self.state = ChannelState::Fading; + + if self.state == ChannelState::Stopped && fader.direction() == FadeDirection::In { + self.sink.set_volume(fader.get_volume()); + } + } + + /// Returns true if either the channels sink reports itself as empty (no + /// more sounds in the queue) or we have forcibly set the channels state to + /// the 'Stopped' state + pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped } + + pub fn get_tag(&self) -> MusicChannelTag { self.tag } + + /// Maintain the fader attached to this channel. If the channel is not + /// fading, no action is taken. + pub fn maintain(&mut self, dt: f32) { + if self.state == ChannelState::Fading { + self.fader.update(dt); + self.sink.set_volume(self.fader.get_volume()); + + if self.fader.is_finished() { + match self.fader.direction() { + FadeDirection::Out => { + self.state = ChannelState::Stopped; + self.sink.stop(); + }, + FadeDirection::In => { + self.state = ChannelState::Playing; + }, + } + } + } + } +} + +/// An SfxChannel uses a positional audio sink, and is designed for short-lived +/// audio which can be spatially controlled, but does not need control over +/// playback or fading/transitions +pub struct SfxChannel { + sink: SpatialSink, pub pos: Vec3, } -// TODO: Implement asynchronous loading -impl Channel { - /// Create an empty channel for future use +impl SfxChannel { pub fn new(device: &Device) -> Self { Self { - id: 0, sink: SpatialSink::new(device, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]), - audio_type: AudioType::None, - state: ChannelState::Stopped, - fader: Fader::fade_in(0.0), - tag: None, pos: Vec3::zero(), } } @@ -57,31 +131,13 @@ impl Channel { S::Item: Send, ::Item: std::fmt::Debug, { - self.state = ChannelState::Playing; self.sink.append(source); } - pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped } - - pub fn set_tag(&mut self, tag: Option) { self.tag = tag; } - - pub fn get_tag(&self) -> Option { self.tag } - - pub fn stop(&mut self, fader: Fader) { - self.state = ChannelState::Stopping; - self.fader = fader; - } - - pub fn get_id(&self) -> usize { self.id } - - pub fn set_id(&mut self, new_id: usize) { self.id = new_id; } - - pub fn get_audio_type(&self) -> AudioType { self.audio_type } - - pub fn set_audio_type(&mut self, audio_type: AudioType) { self.audio_type = audio_type; } - pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume); } + pub fn is_done(&self) -> bool { self.sink.empty() } + pub fn set_emitter_position(&mut self, pos: [f32; 3]) { self.sink.set_emitter_position(pos); } pub fn set_left_ear_position(&mut self, pos: [f32; 3]) { self.sink.set_left_ear_position(pos); } @@ -89,20 +145,4 @@ impl Channel { pub fn set_right_ear_position(&mut self, pos: [f32; 3]) { self.sink.set_right_ear_position(pos); } - - pub fn update(&mut self, dt: f32) { - match self.state { - // ChannelState::Init | ChannelState::ToPlay | ChannelState::Loading => {} - ChannelState::Playing => {}, - ChannelState::Stopping => { - self.fader.update(dt); - self.sink.set_volume(self.fader.get_volume()); - - if self.fader.is_finished() { - self.state = ChannelState::Stopped; - } - }, - ChannelState::Stopped => {}, - } - } } diff --git a/voxygen/src/audio/fader.rs b/voxygen/src/audio/fader.rs index dd835a2045..1af58c8c20 100644 --- a/voxygen/src/audio/fader.rs +++ b/voxygen/src/audio/fader.rs @@ -6,13 +6,18 @@ pub struct Fader { volume_to: f32, is_running: bool, } +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum FadeDirection { + In, + Out, +} fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b } impl Fader { - pub fn fade(time: f32, volume_from: f32, volume_to: f32) -> Self { + pub fn fade(length: f32, volume_from: f32, volume_to: f32) -> Self { Self { - length: time, + length, running_time: 0.0, volume_from, volume_to, @@ -20,23 +25,28 @@ impl Fader { } } - pub fn fade_in(time: f32) -> Self { - Self { - length: time, - running_time: 0.0, - volume_from: 0.0, - volume_to: 1.0, - is_running: true, + pub fn fade_in(time: f32, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) } + + pub fn fade_out(time: f32, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) } + + pub fn update_target_volume(&mut self, volume: f32) { + match self.direction() { + FadeDirection::In => { + self.volume_to = volume; + }, + FadeDirection::Out => { + if self.get_volume() > volume { + self.volume_from = volume; + } + }, } } - pub fn fade_out(time: f32, volume_from: f32) -> Self { - Self { - length: time, - running_time: 0.0, - volume_from, - volume_to: 0.0, - is_running: true, + pub fn direction(&self) -> FadeDirection { + if self.volume_to < self.volume_from { + FadeDirection::Out + } else { + FadeDirection::In } } @@ -60,3 +70,107 @@ impl Fader { pub fn is_finished(&self) -> bool { self.running_time >= self.length || !self.is_running } } + +/// Returns a stopped fader with no running duration +impl Default for Fader { + fn default() -> Self { + Self { + length: 0.0, + running_time: 0.0, + volume_from: 0.0, + volume_to: 1.0, + is_running: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fade_direction_in() { + let fader = Fader::fade_in(10.0, 0.0); + + assert_eq!(fader.direction(), FadeDirection::In); + } + + #[test] + fn fade_direction_out() { + let fader = Fader::fade_out(10.0, 1.0); + + assert_eq!(fader.direction(), FadeDirection::Out); + } + + #[test] + fn fade_out_completes() { + let mut fader = Fader::fade_out(10.0, 1.0); + + // Run for the full duration + fader.update(10.0); + + assert_eq!(fader.get_volume(), 0.0); + assert!(fader.is_finished()); + } + + #[test] + fn update_target_volume_fading_out_when_currently_above() { + let mut fader = Fader::fade_out(20.0, 1.0); + + // After 0.1s, the fader should still be close to 1.0 + fader.update(0.1); + + // Reduce volume to 0.4. We are currently above that. + fader.update_target_volume(0.4); + + // The volume should immediately reduce to < 0.4 on the next update + fader.update(0.1); + + assert!(fader.get_volume() < 0.4) + } + + #[test] + fn update_target_volume_fading_out_when_currently_below() { + let mut fader = Fader::fade_out(10.0, 0.8); + + // After 9s, the fader should be close to 0 + fader.update(9.0); + + // Notify of a volume increase to 1.0. We are already far below that. + fader.update_target_volume(1.0); + + // The fader should be unaffected by the new value, and continue dropping + fader.update(0.1); + + assert!(fader.get_volume() < 0.2); + } + + #[test] + fn update_target_volume_fading_in_when_currently_above() { + let mut fader = Fader::fade_in(10.0, 1.0); + + // After 9s, the fader should be close to 1.0 + fader.update(9.0); + + // Reduce volume to 0.4. We are currently above that. + fader.update_target_volume(0.4); + + // Run out the fader. It's volume should be 0.4 + fader.update(1.0); + + assert_eq!(fader.get_volume(), 0.4); + } + + #[test] + fn update_target_volume_fading_in_when_currently_below() { + let mut fader = Fader::fade_in(20.0, 1.0); + + // After 0.1s, the fader should still be close to 0.0 + fader.update(0.1); + + // Reduce volume to 0.4. The volume_to should be reduced accordingly. + fader.update_target_volume(0.4); + + assert_eq!(fader.volume_to, 0.4); + } +} diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 7e73d9a752..1709649c89 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -4,13 +4,13 @@ pub mod music; pub mod sfx; pub mod soundcache; -use channel::{AudioType, Channel, ChannelTag}; +use channel::{MusicChannel, MusicChannelTag, SfxChannel}; use fader::Fader; use soundcache::SoundCache; use common::assets; use cpal::traits::DeviceTrait; -use rodio::{Decoder, Device}; +use rodio::{source::Source, Decoder, Device}; use vek::*; const FALLOFF: f32 = 0.13; @@ -21,8 +21,8 @@ pub struct AudioFrontend { audio_device: Option, sound_cache: SoundCache, - channels: Vec, - next_channel_id: usize, + music_channels: Vec, + sfx_channels: Vec, sfx_volume: f32, music_volume: f32, @@ -36,21 +36,23 @@ pub struct AudioFrontend { impl AudioFrontend { /// Construct with given device - pub fn new(device: String, channel_num: usize) -> Self { - let mut channels = Vec::with_capacity(channel_num); + pub fn new(device: String, max_sfx_channels: usize) -> Self { + let mut sfx_channels = Vec::with_capacity(max_sfx_channels); let audio_device = get_device_raw(&device); + if let Some(audio_device) = &audio_device { - for _i in 0..channel_num { - channels.push(Channel::new(&audio_device)); + for _ in 0..max_sfx_channels { + sfx_channels.push(SfxChannel::new(&audio_device)); } } + Self { device: device.clone(), device_list: list_devices(), audio_device, sound_cache: SoundCache::new(), - channels, - next_channel_id: 1, + music_channels: Vec::new(), + sfx_channels, sfx_volume: 1.0, music_volume: 1.0, listener_pos: Vec3::zero(), @@ -67,8 +69,8 @@ impl AudioFrontend { device_list: Vec::new(), audio_device: None, sound_cache: SoundCache::new(), - channels: Vec::new(), - next_channel_id: 1, + music_channels: Vec::new(), + sfx_channels: Vec::new(), sfx_volume: 1.0, music_volume: 1.0, listener_pos: Vec3::zero(), @@ -78,75 +80,89 @@ impl AudioFrontend { } } - /// Maintain audio + /// Drop any unused music channels, and update their faders pub fn maintain(&mut self, dt: f32) { - for channel in self.channels.iter_mut() { - channel.update(dt); + self.music_channels.retain(|c| !c.is_done()); + + for channel in self.music_channels.iter_mut() { + channel.maintain(dt); } } - pub fn get_channel( + fn get_sfx_channel(&mut self) -> Option<&mut SfxChannel> { + if self.audio_device.is_some() { + if let Some(channel) = self.sfx_channels.iter_mut().find(|c| c.is_done()) { + channel.set_volume(self.sfx_volume); + + return Some(channel); + } + } + + None + } + + /// Retrieve a music channel from the channel list. This inspects the + /// MusicChannelTag to determine whether we are transitioning between + /// music types and acts accordingly. For example transitioning between + /// `TitleMusic` and `Exploration` should fade out the title channel and + /// fade in a new `Exploration` channel. + fn get_music_channel( &mut self, - audio_type: AudioType, - channel_tag: Option, - ) -> Option<&mut Channel> { - if let Some(channel) = self.channels.iter_mut().find(|c| c.is_done()) { - let id = self.next_channel_id; - self.next_channel_id += 1; + next_channel_tag: MusicChannelTag, + ) -> Option<&mut MusicChannel> { + if let Some(audio_device) = &self.audio_device { + if self.music_channels.is_empty() { + let mut next_music_channel = MusicChannel::new(&audio_device); + next_music_channel.set_volume(self.music_volume); - let volume = match audio_type { - AudioType::Music => self.music_volume, - _ => self.sfx_volume, - }; + self.music_channels.push(next_music_channel); + } else { + let existing_channel = self.music_channels.last_mut()?; - channel.set_id(id); - channel.set_tag(channel_tag); - channel.set_audio_type(audio_type); - channel.set_volume(volume); + if existing_channel.get_tag() != next_channel_tag { + // Fade the existing channel out. It will be removed when the fade completes. + existing_channel.set_fader(Fader::fade_out(2.0, self.music_volume)); - Some(channel) - } else { - None + let mut next_music_channel = MusicChannel::new(&audio_device); + + next_music_channel.set_fader(Fader::fade_in(12.0, self.music_volume)); + + self.music_channels.push(next_music_channel); + } + } } + + self.music_channels.last_mut() } - /// Play specfied sound file. - pub fn play_sound(&mut self, sound: &str, pos: Vec3) -> Option { + pub fn play_sfx(&mut self, sound: &str, pos: Vec3, vol: Option) { if self.audio_device.is_some() { let calc_pos = ((pos - self.listener_pos) * FALLOFF).into_array(); - let sound = self.sound_cache.load_sound(sound); + let sound = self + .sound_cache + .load_sound(sound) + .amplify(vol.unwrap_or(1.0)); let left_ear = self.listener_ear_left.into_array(); let right_ear = self.listener_ear_right.into_array(); - if let Some(channel) = self.get_channel(AudioType::Sfx, None) { + if let Some(channel) = self.get_sfx_channel() { channel.set_emitter_position(calc_pos); channel.set_left_ear_position(left_ear); channel.set_right_ear_position(right_ear); channel.play(sound); - - return Some(channel.get_id()); } } - - None } - pub fn play_music(&mut self, sound: &str, channel_tag: Option) -> Option { - if self.audio_device.is_some() { - if let Some(channel) = self.get_channel(AudioType::Music, channel_tag) { - let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); - let sound = Decoder::new(file).expect("Failed to decode sound"); + fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) { + if let Some(channel) = self.get_music_channel(channel_tag) { + let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); + let sound = Decoder::new(file).expect("Failed to decode sound"); - channel.set_emitter_position([0.0; 3]); - channel.play(sound); - - return Some(channel.get_id()); - } + channel.play(sound, channel_tag); } - - None } pub fn set_listener_pos(&mut self, pos: &Vec3, ori: &Vec3) { @@ -161,8 +177,8 @@ impl AudioFrontend { self.listener_ear_left = pos_left; self.listener_ear_right = pos_right; - for channel in self.channels.iter_mut() { - if !channel.is_done() && channel.get_audio_type() == AudioType::Sfx { + for channel in self.sfx_channels.iter_mut() { + if !channel.is_done() { // TODO: Update this to correctly determine the updated relative position of // the SFX emitter when the player (listener) moves // channel.set_emitter_position( @@ -174,32 +190,18 @@ impl AudioFrontend { } } - pub fn play_title_music(&mut self) -> Option { + pub fn play_title_music(&mut self) { if self.music_enabled() { self.play_music( "voxygen.audio.soundtrack.veloren_title_tune", - Some(ChannelTag::TitleMusic), + MusicChannelTag::TitleMusic, ) - } else { - None } } - pub fn stop_title_music(&mut self) { - let index = self.channels.iter().position(|c| { - !c.is_done() && c.get_tag().is_some() && c.get_tag().unwrap() == ChannelTag::TitleMusic - }); - - if let Some(index) = index { - self.channels[index].stop(Fader::fade_out(1.5, self.music_volume)); - } - } - - pub fn stop_channel(&mut self, channel_id: usize, fader: Fader) { - let index = self.channels.iter().position(|c| c.get_id() == channel_id); - - if let Some(index) = index { - self.channels[index].stop(fader); + pub fn play_exploration_music(&mut self, item: &str) { + if self.music_enabled() { + self.play_music(item, MusicChannelTag::Exploration) } } @@ -214,24 +216,16 @@ impl AudioFrontend { pub fn set_sfx_volume(&mut self, sfx_volume: f32) { self.sfx_volume = sfx_volume; - for channel in self.channels.iter_mut() { - if channel.get_audio_type() == AudioType::Sfx { - channel.set_volume(sfx_volume); - } + for channel in self.sfx_channels.iter_mut() { + channel.set_volume(sfx_volume); } } pub fn set_music_volume(&mut self, music_volume: f32) { self.music_volume = music_volume; - for channel in self.channels.iter_mut() { - if channel.get_audio_type() == AudioType::Music { - if music_volume > 0.0 { - channel.set_volume(music_volume); - } else { - channel.stop(Fader::fade_out(0.0, 0.0)); - } - } + for channel in self.music_channels.iter_mut() { + channel.set_volume(music_volume); } } diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs index 8f6ecc0525..7b46abd016 100644 --- a/voxygen/src/audio/music.rs +++ b/voxygen/src/audio/music.rs @@ -1,4 +1,4 @@ -use crate::audio::{channel::ChannelTag, AudioFrontend}; +use crate::audio::AudioFrontend; use client::Client; use common::assets; use rand::{seq::IteratorRandom, thread_rng}; @@ -31,7 +31,6 @@ pub struct MusicMgr { soundtrack: SoundtrackCollection, began_playing: Instant, next_track_change: f64, - current_music: Option, last_track: String, } @@ -41,7 +40,6 @@ impl MusicMgr { soundtrack: Self::load_soundtrack_items(), began_playing: Instant::now(), next_track_change: 0.0, - current_music: None, last_track: String::from("None"), } } @@ -50,15 +48,15 @@ impl MusicMgr { if audio.music_enabled() && self.began_playing.elapsed().as_secs_f64() > self.next_track_change { - self.current_music = self.play_random_track(audio, client); + self.play_random_track(audio, client); } } - fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) -> Option { + fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) { const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0; let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32; - let current_period_of_day = self.get_current_day_period(game_time); + let current_period_of_day = Self::get_current_day_period(game_time); let mut rng = thread_rng(); let track = self @@ -79,10 +77,10 @@ impl MusicMgr { self.began_playing = Instant::now(); self.next_track_change = track.length + SILENCE_BETWEEN_TRACKS_SECONDS; - audio.play_music(&track.path, Some(ChannelTag::Soundtrack)) + audio.play_exploration_music(&track.path); } - fn get_current_day_period(&self, game_time: u32) -> DayPeriod { + fn get_current_day_period(game_time: u32) -> DayPeriod { if game_time > DAY_START_SECONDS && game_time < DAY_END_SECONDS { DayPeriod::Day } else { diff --git a/voxygen/src/audio/sfx/event_mapper/movement.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs similarity index 53% rename from voxygen/src/audio/sfx/event_mapper/movement.rs rename to voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 00e80b6815..05191d9049 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -5,7 +5,7 @@ use crate::audio::sfx::{SfxTriggerItem, SfxTriggers}; use client::Client; use common::{ - comp::{Body, CharacterState, Pos, Vel}, + comp::{Body, CharacterState, Item, ItemKind, Pos, Stats, ToolData, Vel}, event::{EventBus, SfxEvent, SfxEventItem}, }; use hashbrown::HashMap; @@ -16,6 +16,7 @@ use vek::*; #[derive(Clone)] struct LastSfxEvent { event: SfxEvent, + weapon_drawn: bool, time: Instant, } @@ -31,7 +32,7 @@ impl MovementEventMapper { } pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { - const SFX_DIST_LIMIT_SQR: f32 = 22500.0; + const SFX_DIST_LIMIT_SQR: f32 = 20000.0; let ecs = client.state().ecs(); let player_position = ecs @@ -39,11 +40,12 @@ impl MovementEventMapper { .get(client.entity()) .map_or(Vec3::zero(), |pos| pos.0); - for (entity, pos, vel, body, character) in ( + for (entity, pos, vel, body, stats, character) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), + &ecs.read_storage::(), ecs.read_storage::().maybe(), ) .join() @@ -57,27 +59,36 @@ impl MovementEventMapper { .entry(entity) .or_insert_with(|| LastSfxEvent { event: SfxEvent::Idle, + weapon_drawn: false, time: Instant::now(), }); let mapped_event = match body { - Body::Humanoid(_) => Self::map_movement_event(character, state.event.clone()), - Body::QuadrupedMedium(_) => { - // TODO: Quadriped running sfx - SfxEvent::Idle + Body::Humanoid(_) => Self::map_movement_event(character, state, vel.0, stats), + Body::QuadrupedMedium(_) + | Body::QuadrupedSmall(_) + | Body::BirdMedium(_) + | Body::BirdSmall(_) + | Body::BipedLarge(_) => { + Self::map_non_humanoid_movement_event(character, vel.0) }, - _ => SfxEvent::Idle, + _ => SfxEvent::Idle, // Ignore fish, critters, etc... }; // Check for SFX config entry for this movement if Self::should_emit(state, triggers.get_key_value(&mapped_event)) { ecs.read_resource::>() .emitter() - .emit(SfxEventItem::new(mapped_event, Some(pos.0))); + .emit(SfxEventItem::new( + mapped_event, + Some(pos.0), + Some(Self::get_volume_for_body_type(body)), + )); // Update the last play time state.event = mapped_event; state.time = Instant::now(); + state.weapon_drawn = character.is_wielded(); } else { // Keep the last event, it may not have an SFX trigger but it helps us determine // the next one @@ -95,7 +106,7 @@ impl MovementEventMapper { /// they have not triggered an event for > n seconds. This prevents /// stale records from bloating the Map size. fn cleanup(&mut self, player: EcsEntity) { - const TRACKING_TIMEOUT: u64 = 15; + const TRACKING_TIMEOUT: u64 = 10; let now = Instant::now(); self.event_history.retain(|entity, event| { @@ -129,8 +140,33 @@ impl MovementEventMapper { /// as opening or closing the glider. These methods translate those /// entity states with some additional data into more specific /// `SfxEvent`'s which we attach sounds to - fn map_movement_event(current_event: &CharacterState, previous_event: SfxEvent) -> SfxEvent { - match (previous_event, current_event) { + fn map_movement_event( + current_event: &CharacterState, + previous_event: &LastSfxEvent, + vel: Vec3, + stats: &Stats, + ) -> SfxEvent { + // Handle any weapon wielding changes up front. Doing so here first simplifies + // handling the movement/action state later, since they don't require querying + // stats or previous wield state. + if let Some(Item { + kind: ItemKind::Tool(ToolData { kind, .. }), + .. + }) = stats.equipment.main + { + if let Some(wield_event) = + match (previous_event.weapon_drawn, current_event.is_wielded()) { + (false, true) => Some(SfxEvent::Wield(kind)), + (true, false) => Some(SfxEvent::Unwield(kind)), + _ => None, + } + { + return wield_event; + } + } + + // Match all other Movemement and Action states + match (previous_event.event, current_event) { (_, CharacterState::Roll(_)) => SfxEvent::Roll, (_, CharacterState::Climb(_)) => SfxEvent::Climb, (SfxEvent::Glide, CharacterState::Idle(_)) => SfxEvent::GliderClose, @@ -144,135 +180,33 @@ impl MovementEventMapper { _ => SfxEvent::Idle, } } -} -#[cfg(test)] -mod tests { - use super::*; - use common::{comp::CharacterState, event::SfxEvent}; - use std::time::{Duration, Instant}; - - #[test] - fn no_item_config_no_emit() { - let last_sfx_event = LastSfxEvent { - event: SfxEvent::Idle, - time: Instant::now(), - }; - - let result = MovementEventMapper::should_emit(&last_sfx_event, None); - - assert_eq!(result, false); + /// Maps a limited set of movements for other non-humanoid entities + fn map_non_humanoid_movement_event(current_event: &CharacterState, vel: Vec3) -> SfxEvent { + if let CharacterState::Idle(_) = current_event { + if vel.magnitude() > 0.1 { + SfxEvent::Run + } else { + SfxEvent::Idle + } + } else { + SfxEvent::Idle + } } - #[test] - fn config_but_played_since_threshold_no_emit() { - let event = SfxEvent::Run; - - let trigger_item = SfxTriggerItem { - files: vec![String::from("some.path.to.sfx.file")], - threshold: 1.0, - }; - - // Triggered a 'Run' 0 seconds ago - let last_sfx_event = LastSfxEvent { - event: SfxEvent::Run, - time: Instant::now(), - }; - - let result = - MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item))); - - assert_eq!(result, false); - } - - #[test] - fn config_and_not_played_since_threshold_emits() { - let event = SfxEvent::Run; - - let trigger_item = SfxTriggerItem { - files: vec![String::from("some.path.to.sfx.file")], - threshold: 0.5, - }; - - let last_sfx_event = LastSfxEvent { - event: SfxEvent::Idle, - time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(), - }; - - let result = - MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item))); - - assert_eq!(result, true); - } - - #[test] - fn same_previous_event_elapsed_emits() { - let event = SfxEvent::Run; - - let trigger_item = SfxTriggerItem { - files: vec![String::from("some.path.to.sfx.file")], - threshold: 0.5, - }; - - let last_sfx_event = LastSfxEvent { - event: SfxEvent::Run, - time: Instant::now() - .checked_sub(Duration::from_millis(500)) - .unwrap(), - }; - - let result = - MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item))); - - assert_eq!(result, true); - } - - #[test] - fn maps_idle() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Idle(None), SfxEvent::Idle); - assert_eq!(result, SfxEvent::Idle); - } - - #[test] - fn maps_roll() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Roll(None), SfxEvent::Run); - assert_eq!(result, SfxEvent::Roll); - } - - #[test] - fn maps_land_on_ground_to_run() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Idle(None), SfxEvent::Fall); - assert_eq!(result, SfxEvent::Run); - } - - #[test] - fn maps_glider_open() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Glide(None), SfxEvent::Jump); - assert_eq!(result, SfxEvent::GliderOpen); - } - - #[test] - fn maps_glide() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Glide(None), SfxEvent::Glide); - assert_eq!(result, SfxEvent::Glide); - } - - #[test] - fn maps_glider_close_when_closing_mid_flight() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Idle(None), SfxEvent::Glide); - assert_eq!(result, SfxEvent::GliderClose); - } - - #[test] - fn maps_glider_close_when_landing() { - let result = - MovementEventMapper::map_movement_event(&CharacterState::Idle(None), SfxEvent::Glide); - assert_eq!(result, SfxEvent::GliderClose); + /// Returns a relative volume value for a body type. This helps us emit sfx + /// at a volume appropriate fot the entity we are emitting the event for + fn get_volume_for_body_type(body: &Body) -> f32 { + match body { + Body::Humanoid(_) => 0.9, + Body::QuadrupedSmall(_) => 0.3, + Body::QuadrupedMedium(_) => 0.7, + Body::BirdMedium(_) => 0.3, + Body::BirdSmall(_) => 0.2, + Body::BipedLarge(_) => 1.0, + _ => 0.9, + } } } + +#[cfg(test)] mod tests; diff --git a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs new file mode 100644 index 0000000000..251efba8a5 --- /dev/null +++ b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs @@ -0,0 +1,456 @@ +use super::*; +use common::{ + assets, + comp::{ + bird_small, humanoid, item::Tool, quadruped_medium, quadruped_small, ActionState, Body, + MovementState, Stats, + }, + event::SfxEvent, +}; +use std::time::{Duration, Instant}; + +#[test] +fn no_item_config_no_emit() { + let last_sfx_event = LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now(), + }; + + let result = MovementEventMapper::should_emit(&last_sfx_event, None); + + assert_eq!(result, false); +} + +#[test] +fn config_but_played_since_threshold_no_emit() { + let event = SfxEvent::Run; + + let trigger_item = SfxTriggerItem { + files: vec![String::from("some.path.to.sfx.file")], + threshold: 1.0, + }; + + // Triggered a 'Run' 0 seconds ago + let last_sfx_event = LastSfxEvent { + event: SfxEvent::Run, + weapon_drawn: false, + time: Instant::now(), + }; + + let result = MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item))); + + assert_eq!(result, false); +} + +#[test] +fn config_and_not_played_since_threshold_emits() { + let event = SfxEvent::Run; + + let trigger_item = SfxTriggerItem { + files: vec![String::from("some.path.to.sfx.file")], + threshold: 0.5, + }; + + let last_sfx_event = LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(), + }; + + let result = MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item))); + + assert_eq!(result, true); +} + +#[test] +fn same_previous_event_elapsed_emits() { + let event = SfxEvent::Run; + + let trigger_item = SfxTriggerItem { + files: vec![String::from("some.path.to.sfx.file")], + threshold: 0.5, + }; + + let last_sfx_event = LastSfxEvent { + event: SfxEvent::Run, + weapon_drawn: false, + time: Instant::now() + .checked_sub(Duration::from_millis(500)) + .unwrap(), + }; + + let result = MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item))); + + assert_eq!(result, true); +} + +#[test] +fn maps_idle() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Stand, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Idle); +} + +#[test] +fn maps_run_with_sufficient_velocity() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Run, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::new(0.5, 0.8, 0.0), + &stats, + ); + + assert_eq!(result, SfxEvent::Run); +} + +#[test] +fn does_not_map_run_with_insufficient_velocity() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Run, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::new(0.02, 0.0001, 0.0), + &stats, + ); + + assert_eq!(result, SfxEvent::Idle); +} + +#[test] +fn maps_roll() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + action: ActionState::Roll { + time_left: Duration::new(1, 0), + was_wielding: false, + }, + movement: MovementState::Run, + }, + &LastSfxEvent { + event: SfxEvent::Run, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Roll); +} + +#[test] +fn maps_fall() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Fall, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Fall, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Fall); +} + +#[test] +fn maps_land_on_ground_to_run() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Stand, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Fall, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Run); +} + +#[test] +fn maps_glider_open() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Glide, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Jump, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::GliderOpen); +} + +#[test] +fn maps_glide() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Glide, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Glide, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Glide); +} + +#[test] +fn maps_glider_close_when_closing_mid_flight() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Fall, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Glide, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::GliderClose); +} + +#[test] +fn maps_glider_close_when_landing() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Stand, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Glide, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::GliderClose); +} + +#[test] +fn maps_wield() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + Some(assets::load_expect_cloned( + "common.items.weapons.starter_sword", + )), + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Stand, + action: ActionState::Wield { + time_left: Duration::from_millis(800), + }, + }, + &LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Wield(Tool::Sword)); +} + +#[test] +fn maps_unwield() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + Some(assets::load_expect_cloned( + "common.items.weapons.starter_axe", + )), + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Stand, + action: ActionState::Idle, + }, + &LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: true, + time: Instant::now(), + }, + Vec3::zero(), + &stats, + ); + + assert_eq!(result, SfxEvent::Unwield(Tool::Axe)); +} + +#[test] +fn does_not_map_wield_when_no_main_weapon() { + let stats = Stats::new( + String::from("test"), + Body::Humanoid(humanoid::Body::random()), + None, + ); + + let result = MovementEventMapper::map_movement_event( + &CharacterState { + movement: MovementState::Run, + action: ActionState::Wield { + time_left: Duration::from_millis(600), + }, + }, + &LastSfxEvent { + event: SfxEvent::Idle, + weapon_drawn: false, + time: Instant::now(), + }, + Vec3::new(0.5, 0.8, 0.0), + &stats, + ); + + assert_eq!(result, SfxEvent::Run); +} + +#[test] +fn maps_quadrupeds_running() { + let result = MovementEventMapper::map_non_humanoid_movement_event( + &CharacterState { + movement: MovementState::Run, + action: ActionState::Idle, + }, + Vec3::new(0.5, 0.8, 0.0), + ); + + assert_eq!(result, SfxEvent::Run); +} + +#[test] +fn determines_relative_volumes() { + let human = + MovementEventMapper::get_volume_for_body_type(&Body::Humanoid(humanoid::Body::random())); + + let quadruped_medium = MovementEventMapper::get_volume_for_body_type(&Body::QuadrupedMedium( + quadruped_medium::Body::random(), + )); + + let quadruped_small = MovementEventMapper::get_volume_for_body_type(&Body::QuadrupedSmall( + quadruped_small::Body::random(), + )); + + let bird_small = + MovementEventMapper::get_volume_for_body_type(&Body::BirdSmall(bird_small::Body::random())); + + assert!(quadruped_medium < human); + assert!(quadruped_small < quadruped_medium); + assert!(bird_small < quadruped_small); +} diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index cbd6d50f5b..2af778a0ef 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -91,7 +91,7 @@ impl SfxMgr { }, }; - audio.play_sound(sfx_file, position); + audio.play_sfx(sfx_file, position, event.vol); } } } diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 3869b778f1..22d763ed42 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,9 +1,9 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::{ItemImgs, ItemKey}, - Event as HudEvent, Fonts, TEXT_COLOR, + Event as HudEvent, TEXT_COLOR, }; -use crate::ui::{ImageFrame, Tooltip, TooltipManager, Tooltipable}; +use crate::ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}; use client::Client; use conrod_core::{ color, image, @@ -35,7 +35,7 @@ pub struct Bag<'a> { client: &'a Client, imgs: &'a Imgs, item_imgs: &'a ItemImgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, rot_imgs: &'a ImgsRot, @@ -48,7 +48,7 @@ impl<'a> Bag<'a> { client: &'a Client, imgs: &'a Imgs, item_imgs: &'a ItemImgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, pulse: f32, @@ -118,10 +118,11 @@ impl<'a> Widget for Bag<'a> { 5.0, ) }) - .title_font_size(15) + .title_font_size(self.fonts.cyri.scale(15)) .parent(ui.window) - .desc_font_size(12) + .desc_font_size(self.fonts.cyri.scale(12)) .title_text_color(TEXT_COLOR) + .font_id(self.fonts.cyri.conrod_id) .desc_text_color(TEXT_COLOR); // Bag parts diff --git a/voxygen/src/hud/buttons.rs b/voxygen/src/hud/buttons.rs index d37a186dcf..65c0a209c4 100644 --- a/voxygen/src/hud/buttons.rs +++ b/voxygen/src/hud/buttons.rs @@ -3,8 +3,8 @@ use conrod_core::{ widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; -use super::{img_ids::Imgs, Fonts, Windows, TEXT_COLOR}; -use crate::ui::ToggleButton; +use super::{img_ids::Imgs, Windows, TEXT_COLOR}; +use crate::ui::{fonts::ConrodVoxygenFonts, ToggleButton}; widget_ids! { struct Ids { @@ -31,7 +31,7 @@ pub struct Buttons<'a> { show_bag: bool, imgs: &'a Imgs, - _fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, } @@ -42,14 +42,14 @@ impl<'a> Buttons<'a> { show_map: bool, show_bag: bool, imgs: &'a Imgs, - _fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, ) -> Self { Self { open_windows, show_map, show_bag, imgs, - _fonts, + fonts, common: widget::CommonBuilder::default(), } } @@ -101,6 +101,7 @@ impl<'a> Widget for Buttons<'a> { Text::new("B") .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) .font_size(10) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.bag_text, ui); } else { @@ -111,6 +112,7 @@ impl<'a> Widget for Buttons<'a> { Text::new("B") .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) .font_size(10) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.bag_text, ui); } @@ -122,6 +124,7 @@ impl<'a> Widget for Buttons<'a> { .hover_image(self.imgs.settings_hover) .press_image(self.imgs.settings_press) .label("N") + .label_font_id(self.fonts.cyri.conrod_id) .label_font_size(10) .label_color(TEXT_COLOR) .label_y(conrod_core::position::Relative::Scalar(-7.0)) @@ -144,6 +147,7 @@ impl<'a> Widget for Buttons<'a> { .hover_image(self.imgs.map_hover) .press_image(self.imgs.map_press) .label("M") + .label_font_id(self.fonts.cyri.conrod_id) .label_font_size(10) .label_color(TEXT_COLOR) .label_y(conrod_core::position::Relative::Scalar(-7.0)) @@ -185,6 +189,7 @@ impl<'a> Widget for Buttons<'a> { .hover_image(self.imgs.social_hover) .press_image(self.imgs.social_press) .label("O") + .label_font_id(self.fonts.cyri.conrod_id) .label_font_size(10) .label_color(TEXT_COLOR) .label_y(conrod_core::position::Relative::Scalar(-7.0)) @@ -202,6 +207,7 @@ impl<'a> Widget for Buttons<'a> { .hover_image(self.imgs.spellbook_hover) .press_image(self.imgs.spellbook_press) .label("P") + .label_font_id(self.fonts.cyri.conrod_id) .label_font_size(10) .label_color(TEXT_COLOR) .label_y(conrod_core::position::Relative::Scalar(-7.0)) @@ -219,6 +225,7 @@ impl<'a> Widget for Buttons<'a> { .hover_image(self.imgs.character_hover) .press_image(self.imgs.character_press) .label("C") + .label_font_id(self.fonts.cyri.conrod_id) .label_font_size(10) .label_color(TEXT_COLOR) .label_y(conrod_core::position::Relative::Scalar(-7.0)) @@ -236,6 +243,7 @@ impl<'a> Widget for Buttons<'a> { .hover_image(self.imgs.qlog_hover) .press_image(self.imgs.qlog_press) .label("L") + .label_font_id(self.fonts.cyri.conrod_id) .label_font_size(10) .label_color(TEXT_COLOR) .label_y(conrod_core::position::Relative::Scalar(-7.0)) diff --git a/voxygen/src/hud/character_window.rs b/voxygen/src/hud/character_window.rs index ecf972250d..48b460a16f 100644 --- a/voxygen/src/hud/character_window.rs +++ b/voxygen/src/hud/character_window.rs @@ -1,5 +1,5 @@ -use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR, XP_COLOR}; -use crate::i18n::VoxygenLocalization; +use super::{img_ids::Imgs, Show, TEXT_COLOR, XP_COLOR}; +use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts}; use common::comp::Stats; use conrod_core::{ color, @@ -71,7 +71,7 @@ widget_ids! { pub struct CharacterWindow<'a> { _show: &'a Show, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, stats: &'a Stats, localized_strings: &'a std::sync::Arc, @@ -84,7 +84,7 @@ impl<'a> CharacterWindow<'a> { _show: &'a Show, stats: &'a Stats, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, ) -> Self { Self { @@ -155,8 +155,8 @@ impl<'a> Widget for CharacterWindow<'a> { .get("character_window.character_name"), ) .mid_top_with_margin_on(state.charwindow_frame, 6.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .set(state.charwindow_title, ui); @@ -352,8 +352,8 @@ impl<'a> Widget for CharacterWindow<'a> { // Level Text::new(&level) .mid_top_with_margin_on(state.charwindow_rectangle, 10.0) - .font_id(self.fonts.cyri) - .font_size(30) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(30)) .color(TEXT_COLOR) .set(state.charwindow_tab1_level, ui); @@ -376,8 +376,8 @@ impl<'a> Widget for CharacterWindow<'a> { // Exp-Text Text::new(&exp_treshold) .mid_top_with_margin_on(state.charwindow_tab1_expbar, 10.0) - .font_id(self.fonts.cyri) - .font_size(15) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(15)) .color(TEXT_COLOR) .set(state.charwindow_tab1_exp, ui); @@ -395,8 +395,8 @@ impl<'a> Widget for CharacterWindow<'a> { .get("character_window.character_stats"), ) .top_left_with_margins_on(state.charwindow_rectangle, 140.0, 5.0) - .font_id(self.fonts.cyri) - .font_size(16) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(16)) .color(TEXT_COLOR) .set(state.charwindow_tab1_statnames, ui); @@ -406,8 +406,8 @@ impl<'a> Widget for CharacterWindow<'a> { self.stats.endurance, self.stats.fitness, self.stats.willpower )) .top_right_with_margins_on(state.charwindow_rectangle, 140.0, 5.0) - .font_id(self.fonts.cyri) - .font_size(16) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(16)) .color(TEXT_COLOR) .set(state.charwindow_tab1_stats, ui); diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 1b7b1859c8..2a3197fdaa 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -1,8 +1,8 @@ use super::{ - img_ids::Imgs, Fonts, BROADCAST_COLOR, FACTION_COLOR, GAME_UPDATE_COLOR, GROUP_COLOR, - KILL_COLOR, META_COLOR, PRIVATE_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, + img_ids::Imgs, BROADCAST_COLOR, FACTION_COLOR, GAME_UPDATE_COLOR, GROUP_COLOR, KILL_COLOR, + META_COLOR, PRIVATE_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, }; -use crate::GlobalState; +use crate::{ui::fonts::ConrodVoxygenFonts, GlobalState}; use client::Event as ClientEvent; use common::{msg::validate_chat_msg, ChatType}; use conrod_core::{ @@ -34,7 +34,7 @@ pub struct Chat<'a> { global_state: &'a GlobalState, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -48,7 +48,7 @@ impl<'a> Chat<'a> { new_messages: &'a mut VecDeque, global_state: &'a GlobalState, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, ) -> Self { Self { new_messages, @@ -187,8 +187,8 @@ impl<'a> Widget for Chat<'a> { .restrict_to_height(false) .color(TEXT_COLOR) .line_spacing(2.0) - .font_size(15) - .font_id(self.fonts.opensans); + .font_size(self.fonts.opensans.scale(15)) + .font_id(self.fonts.opensans.conrod_id); if let Some(pos) = self.force_cursor { text_edit = text_edit.cursor_pos(pos); @@ -251,8 +251,8 @@ impl<'a> Widget for Chat<'a> { ChatType::Kill => KILL_COLOR, }; let text = Text::new(&message) - .font_size(15) - .font_id(self.fonts.opensans) + .font_size(self.fonts.opensans.scale(15)) + .font_id(self.fonts.opensans.conrod_id) .w(470.0) .color(color) .line_spacing(2.0); @@ -270,8 +270,8 @@ impl<'a> Widget for Chat<'a> { // Needs to be larger than the space above. Some( Text::new("") - .font_size(6) - .font_id(self.fonts.opensans) + .font_size(self.fonts.opensans.scale(6)) + .font_id(self.fonts.opensans.conrod_id) .w(470.0), ) }; diff --git a/voxygen/src/hud/esc_menu.rs b/voxygen/src/hud/esc_menu.rs index 8743805b06..a1d547107f 100644 --- a/voxygen/src/hud/esc_menu.rs +++ b/voxygen/src/hud/esc_menu.rs @@ -1,14 +1,15 @@ -use super::{img_ids::Imgs, settings_window::SettingsTab, Fonts, TEXT_COLOR}; -use crate::i18n::VoxygenLocalization; +use super::{img_ids::Imgs, settings_window::SettingsTab, TEXT_COLOR}; +use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts}; use conrod_core::{ widget::{self, Button, Image}, - widget_ids, Labelable, Positionable, Sizeable, Widget, WidgetCommon, + widget_ids, Color, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; widget_ids! { struct Ids { esc_bg, fireplace, + banner_top, menu_button_1, menu_button_2, menu_button_3, @@ -21,7 +22,7 @@ widget_ids! { #[derive(WidgetCommon)] pub struct EscMenu<'a> { imgs: &'a Imgs, - _fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, #[conrod(common_builder)] @@ -31,12 +32,12 @@ pub struct EscMenu<'a> { impl<'a> EscMenu<'a> { pub fn new( imgs: &'a Imgs, - _fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, ) -> Self { Self { imgs, - _fonts, + fonts, localized_strings, common: widget::CommonBuilder::default(), } @@ -72,25 +73,33 @@ impl<'a> Widget for EscMenu<'a> { let widget::UpdateArgs { state, ui, .. } = args; Image::new(self.imgs.esc_frame) - .w_h(240.0, 440.0) + .w_h(240.0, 380.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.9))) .middle_of(ui.window) .set(state.ids.esc_bg, ui); - Image::new(self.imgs.fireplace) - .w_h(210.0, 60.0) - .mid_top_with_margin_on(state.ids.esc_bg, 15.0) - .set(state.ids.fireplace, ui); + Image::new(self.imgs.banner_top) + .w_h(250.0, 34.0) + .mid_top_with_margin_on(state.ids.esc_bg, -34.0) + .set(state.ids.banner_top, ui); + + /*Image::new(self.imgs.fireplace) + .w_h(210.0, 60.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8))) + .mid_top_with_margin_on(state.ids.esc_bg, 5.0) + .set(state.ids.fireplace, ui);*/ // Resume if Button::image(self.imgs.button) - .mid_bottom_with_margin_on(state.ids.fireplace, -55.0) + .mid_bottom_with_margin_on(state.ids.banner_top, -60.0) .w_h(210.0, 50.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label(&self.localized_strings.get("common.resume")) .label_y(conrod_core::position::Relative::Scalar(3.0)) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.menu_button_1, ui) .was_clicked() { @@ -106,7 +115,8 @@ impl<'a> Widget for EscMenu<'a> { .label(&self.localized_strings.get("common.settings")) .label_y(conrod_core::position::Relative::Scalar(3.0)) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.menu_button_2, ui) .was_clicked() { @@ -121,7 +131,8 @@ impl<'a> Widget for EscMenu<'a> { .label(&self.localized_strings.get("common.controls")) .label_y(conrod_core::position::Relative::Scalar(3.0)) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.menu_button_3, ui) .was_clicked() { @@ -136,7 +147,8 @@ impl<'a> Widget for EscMenu<'a> { .label(&self.localized_strings.get("common.characters")) .label_y(conrod_core::position::Relative::Scalar(3.0)) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.menu_button_4, ui) .was_clicked() { @@ -151,7 +163,8 @@ impl<'a> Widget for EscMenu<'a> { .label(&self.localized_strings.get("esc_menu.logout")) .label_y(conrod_core::position::Relative::Scalar(3.0)) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.menu_button_5, ui) .was_clicked() { @@ -166,7 +179,8 @@ impl<'a> Widget for EscMenu<'a> { .label(&self.localized_strings.get("esc_menu.quit_game")) .label_y(conrod_core::position::Relative::Scalar(3.0)) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.menu_button_6, ui) .was_clicked() { diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 84a9610ed1..8139982d88 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -8,6 +8,13 @@ rotation_image_ids! { // Tooltip Test tt_side: "voxygen/element/frames/tt_test_edge", tt_corner: "voxygen/element/frames/tt_test_corner_tr", + +////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + // Minimap + indicator_mmap_small: "voxygen.element.buttons.indicator_mmap_small", } } @@ -106,6 +113,9 @@ image_ids! { //////////////////////////////////////////////////////////////////////// + // Esc-Menu + fireplace: "voxygen.element.misc_bg.fireplace", + // Skill Icons twohsword_m1: "voxygen.element.icons.2hsword_m1", twohsword_m2: "voxygen.element.icons.2hsword_m2", @@ -131,7 +141,6 @@ image_ids! { skull_2: "voxygen.element.icons.skull_2", // Map - map_indicator: "voxygen.element.buttons.map_indicator", indicator_mmap: "voxygen.element.buttons.indicator_mmap", indicator_mmap_2: "voxygen.element.buttons.indicator_mmap_2", indicator_mmap_3: "voxygen.element.buttons.indicator_mmap_3", @@ -169,7 +178,7 @@ image_ids! { mmap_open_press: "voxygen.element.buttons.button_mmap_open_press", mmap_plus: "voxygen.element.buttons.min_plus.mmap_button-plus", mmap_plus_hover: "voxygen.element.buttons.min_plus.mmap_button-plus_hover", - mmap_plus_press: "voxygen.element.buttons.min_plus.mmap_button-plus_hover", + mmap_plus_press: "voxygen.element.buttons.min_plus.mmap_button-plus_press", mmap_minus: "voxygen.element.buttons.min_plus.mmap_button-min", mmap_minus_hover: "voxygen.element.buttons.min_plus.mmap_button-min_hover", mmap_minus_press: "voxygen.element.buttons.min_plus.mmap_button-min_press", @@ -227,11 +236,6 @@ image_ids! { close_button_hover: "voxygen.element.buttons.x_hover", close_button_press: "voxygen.element.buttons.x_press", - // Esc-Menu - fireplace: "voxygen.element.misc_bg.fireplace", - button: "voxygen.element.buttons.button", - button_hover: "voxygen.element.buttons.button_hover", - button_press: "voxygen.element.buttons.button_press", // Items potion_red: "voxygen.voxel.object.potion_red", @@ -257,8 +261,18 @@ image_ids! { death_bg: "voxygen.background.death", hurt_bg: "voxygen.background.hurt", + banner_top: "voxygen.element.frames.banner_top", + + + + // Buttons + button: "voxygen.element.buttons.button", + button_hover: "voxygen.element.buttons.button_hover", + button_press: "voxygen.element.buttons.button_press", + // Enemy Healthbar enemy_health: "voxygen.element.frames.enemybar", + enemy_health_bg: "voxygen.element.frames.enemybar_bg", // Enemy Bar Content: enemy_bar: "voxygen.element.skillbar.enemy_bar_content", // Spell Book Window diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 662d004ccc..0b237c72ec 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -1,14 +1,18 @@ -use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR}; +use super::{ + img_ids::{Imgs, ImgsRot}, + Show, TEXT_COLOR, +}; +use crate::ui::{fonts::ConrodVoxygenFonts, img_ids}; use client::{self, Client}; use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use conrod_core::{ color, - image::Id, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; use specs::WorldExt; use vek::*; + widget_ids! { struct Ids { map_frame, @@ -30,12 +34,13 @@ widget_ids! { pub struct Map<'a> { _show: &'a Show, client: &'a Client, - world_map: (Id, Vec2), + world_map: &'a (img_ids::Rotations, Vec2), imgs: &'a Imgs, - fonts: &'a Fonts, + rot_imgs: &'a ImgsRot, + fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, - pulse: f32, + _pulse: f32, velocity: f32, } impl<'a> Map<'a> { @@ -43,19 +48,21 @@ impl<'a> Map<'a> { show: &'a Show, client: &'a Client, imgs: &'a Imgs, - world_map: (Id, Vec2), - fonts: &'a Fonts, + rot_imgs: &'a ImgsRot, + world_map: &'a (img_ids::Rotations, Vec2), + fonts: &'a ConrodVoxygenFonts, pulse: f32, velocity: f32, ) -> Self { Self { _show: show, imgs, + rot_imgs, world_map, client, fonts, common: widget::CommonBuilder::default(), - pulse, + _pulse: pulse, velocity, } } @@ -143,15 +150,15 @@ impl<'a> Widget for Map<'a> { match self.client.current_chunk() { Some(chunk) => Text::new(chunk.meta().name()) .mid_top_with_margin_on(state.ids.map_bg, 55.0) - .font_size(60) + .font_size(self.fonts.alkhemi.scale(60)) .color(TEXT_COLOR) - .font_id(self.fonts.alkhemi) + .font_id(self.fonts.alkhemi.conrod_id) .parent(state.ids.map_frame_r) .set(state.ids.location_name, ui), None => Text::new(" ") .mid_top_with_margin_on(state.ids.map_bg, 3.0) - .font_size(40) - .font_id(self.fonts.alkhemi) + .font_size(self.fonts.alkhemi.scale(40)) + .font_id(self.fonts.alkhemi.conrod_id) .color(TEXT_COLOR) .set(state.ids.location_name, ui), } @@ -160,9 +167,9 @@ impl<'a> Widget for Map<'a> { let (world_map, worldsize) = self.world_map; let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64); - Image::new(world_map) + Image::new(world_map.none) .middle_of(state.ids.map_bg) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade - 0.1))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade + 0.5))) .w_h(700.0, 700.0) .parent(state.ids.map_bg) .set(state.ids.grid, ui); @@ -177,31 +184,18 @@ impl<'a> Widget for Map<'a> { let x = player_pos.x as f64 / worldsize.x * 700.0; let y = player_pos.y as f64 / worldsize.y * 700.0; - let indic_ani = (self.pulse * 6.0/*animation speed*/).cos()/*starts at 1.0*/ * 0.5 + 0.50; // changes the animation frame - let indic_scale = 1.2; - // Indicator - Image::new(if indic_ani <= 0.3 { - self.imgs.indicator_mmap - } else if indic_ani <= 0.6 { - self.imgs.indicator_mmap_2 - } else { - self.imgs.indicator_mmap_3 - }) - .bottom_left_with_margins_on(state.ids.grid, y, x - (20.0 * 1.2) / 2.0) - .w_h( - 22.0 * 1.2, - if indic_ani <= 0.3 { - 16.0 * indic_scale - } else if indic_ani <= 0.6 { - 23.0 * indic_scale - } else { - 34.0 * indic_scale - }, - ) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade + 0.2))) - .floating(true) - .parent(ui.window) - .set(state.ids.indicator, ui); + let indic_scale = 0.6; + Image::new(self.rot_imgs.indicator_mmap_small.target_north) + .bottom_left_with_margins_on( + state.ids.grid, + y - 37.0 * indic_scale / 2.0, + x - 32.0 * indic_scale / 2.0, + ) + .w_h(32.0 * indic_scale, 37.0 * indic_scale) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) + .floating(true) + .parent(ui.window) + .set(state.ids.indicator, ui); None } diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index fc196c6e5b..7fd5190b32 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -1,9 +1,12 @@ -use super::{img_ids::Imgs, Fonts, Show, HP_COLOR, TEXT_COLOR}; +use super::{ + img_ids::{Imgs, ImgsRot}, + Show, HP_COLOR, TEXT_COLOR, +}; +use crate::ui::{fonts::ConrodVoxygenFonts, img_ids}; use client::{self, Client}; use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use conrod_core::{ - color, - image::Id, + color, position, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; @@ -33,12 +36,11 @@ pub struct MiniMap<'a> { client: &'a Client, imgs: &'a Imgs, - world_map: (Id, Vec2), - fonts: &'a Fonts, + rot_imgs: &'a ImgsRot, + world_map: &'a (img_ids::Rotations, Vec2), + fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, - pulse: f32, - zoom: f32, } impl<'a> MiniMap<'a> { @@ -46,20 +48,18 @@ impl<'a> MiniMap<'a> { show: &'a Show, client: &'a Client, imgs: &'a Imgs, - world_map: (Id, Vec2), - fonts: &'a Fonts, - pulse: f32, - zoom: f32, + rot_imgs: &'a ImgsRot, + world_map: &'a (img_ids::Rotations, Vec2), + fonts: &'a ConrodVoxygenFonts, ) -> Self { Self { show, client, imgs, + rot_imgs, world_map, fonts, common: widget::CommonBuilder::default(), - pulse, - zoom, } } } @@ -69,6 +69,7 @@ pub struct State { last_region_name: Option, last_update: Instant, + zoom: f64, } pub enum Event { @@ -86,6 +87,14 @@ impl<'a> Widget for MiniMap<'a> { last_region_name: None, last_update: Instant::now(), + zoom: { + let min_world_dim = self.world_map.1.reduce_partial_min() as f64; + min_world_dim.min( + min_world_dim + * (TerrainChunkSize::RECT_SIZE.reduce_partial_max() as f64 / 32.0) + * (16.0 / 1024.0), + ) + }, } } @@ -93,53 +102,74 @@ impl<'a> Widget for MiniMap<'a> { fn update(self, args: widget::UpdateArgs) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; - let zoom = self.zoom as f64; + let zoom = state.zoom; if self.show.mini_map { Image::new(self.imgs.mmap_frame) - .w_h(100.0 * 4.0 * zoom, 100.0 * 4.0 * zoom) + .w_h(100.0 * 3.0, 100.0 * 3.0) .top_right_with_margins_on(ui.window, 5.0, 5.0) .set(state.ids.mmap_frame, ui); - Rectangle::fill_with([92.0 * 4.0, 82.0 * 4.0], color::TRANSPARENT) - .mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 4.0 + 4.0 * zoom) + Rectangle::fill_with([92.0 * 3.0, 82.0 * 3.0], color::TRANSPARENT) + .mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 3.0 + 3.0) .set(state.ids.mmap_frame_bg, ui); - // Zoom Buttons - // TODO: Add zoomable minimap - /*if Button::image(self.imgs.mmap_plus) - .w_h(100.0 * 0.2 * zoom, 100.0 * 0.2 * zoom) - .hover_image(self.imgs.mmap_plus_hover) - .press_image(self.imgs.mmap_plus_press) - .top_left_with_margins_on(state.ids.mmap_frame, 0.0, 0.0) - .set(state.ids.mmap_plus, ui) - .was_clicked() - { - if zoom > 0.0 { - zoom = zoom + 1.0 - } else if zoom == 5.0 { - } - } - if Button::image(self.imgs.mmap_minus) - .w_h(100.0 * 0.2 * zoom, 100.0 * 0.2 * zoom) - .hover_image(self.imgs.mmap_minus_hover) - .press_image(self.imgs.mmap_minus_press) - .down_from(state.ids.mmap_plus, 0.0) - .set(state.ids.mmap_minus, ui) - .was_clicked() - { - if zoom < 6.0 { - zoom = zoom - 1.0 - } else if zoom == 0.0 { - } - }*/ - // Map Image + // Map size let (world_map, worldsize) = self.world_map; let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64); - Image::new(world_map) - .middle_of(state.ids.mmap_frame_bg) - .w_h(92.0 * 4.0 * zoom, 82.0 * 4.0 * zoom) - .parent(state.ids.mmap_frame_bg) - .set(state.ids.grid, ui); + + // Zoom Buttons + + // Pressing + multiplies, and - divides, zoom by ZOOM_FACTOR. + const ZOOM_FACTOR: f64 = 2.0; + + // TODO: Either prevent zooming all the way in, *or* see if we can interpolate + // somehow if you zoom in too far. Or both. + let min_zoom = 1.0; + let max_zoom = (worldsize / TerrainChunkSize::RECT_SIZE.map(|e| e as f64)) + .reduce_partial_min()/*.min(f64::MAX)*/; + + // NOTE: Not sure if a button can be clicked while disabled, but we still double + // check for both kinds of zoom to make sure that not only was the + // button clicked, it is also okay to perform the zoom action. + // Note that since `Button::image` has side effects, we must perform + // the `can_zoom_in` and `can_zoom_out` checks after the `&&` to avoid + // undesired early termination. + let can_zoom_in = zoom < max_zoom; + let can_zoom_out = zoom > min_zoom; + + if Button::image(self.imgs.mmap_minus) + .w_h(100.0 * 0.30, 100.0 * 0.30) + .hover_image(self.imgs.mmap_minus_hover) + .press_image(self.imgs.mmap_minus_press) + .top_left_with_margins_on(state.ids.mmap_frame, 0.0, 0.0) + .enabled(can_zoom_out) + .set(state.ids.mmap_minus, ui) + .was_clicked() + && can_zoom_out + { + // Set the image dimensions here, rather than recomputing each time. + let zoom = min_zoom.max(zoom / ZOOM_FACTOR); + state.update(|s| s.zoom = zoom); + // set_image_dims(zoom); + } + if Button::image(self.imgs.mmap_plus) + .w_h(100.0 * 0.30, 100.0 * 0.30) + .hover_image(self.imgs.mmap_plus_hover) + .press_image(self.imgs.mmap_plus_press) + .right_from(state.ids.mmap_minus, 6.0) + .enabled(can_zoom_in) + .set(state.ids.mmap_plus, ui) + .was_clicked() + && can_zoom_in + { + let zoom = max_zoom.min(zoom * ZOOM_FACTOR); + state.update(|s| s.zoom = zoom); + // set_image_dims(zoom); + } + + // Reload zoom in case it changed. + let zoom = state.zoom; + // Coordinates let player_pos = self .client @@ -149,30 +179,36 @@ impl<'a> Widget for MiniMap<'a> { .get(self.client.entity()) .map_or(Vec3::zero(), |pos| pos.0); - let x = player_pos.x as f64 / worldsize.x * 92.0 * 4.0; - let y = player_pos.y as f64 / worldsize.y * 82.0 * 4.0; - let indic_ani = (self.pulse * 6.0).cos() * 0.5 + 0.5; //Animation timer - let indic_scale = 0.8; + // Get map image source rectangle dimensons. + let w_src = worldsize.x / TerrainChunkSize::RECT_SIZE.x as f64 / zoom; + let h_src = worldsize.y / TerrainChunkSize::RECT_SIZE.y as f64 / zoom; + + // Set map image to be centered around player coordinates. + let rect_src = position::Rect::from_xy_dim( + [ + player_pos.x as f64 / TerrainChunkSize::RECT_SIZE.x as f64, + (worldsize.y - player_pos.y as f64) / TerrainChunkSize::RECT_SIZE.y as f64, + ], + [w_src, h_src], + ); + + // Map Image + Image::new(world_map.source_north) + .middle_of(state.ids.mmap_frame_bg) + .w_h(92.0 * 3.0, 82.0 * 3.0) + .parent(state.ids.mmap_frame_bg) + .source_rectangle(rect_src) + .set(state.ids.grid, ui); + // Indicator - Image::new(if indic_ani <= 0.5 { - self.imgs.indicator_mmap - } else { - self.imgs.indicator_mmap_2 - }) - .bottom_left_with_margins_on(state.ids.grid, y, x - 5.0) - .w_h( - // Animation frames depening on timer value from 0.0 to 1.0 - 22.0 * 0.8, - if indic_ani <= 0.5 { - 18.0 * indic_scale - } else { - 23.0 * indic_scale - }, - ) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .floating(true) - .parent(ui.window) - .set(state.ids.indicator, ui); + let ind_scale = 0.4; + Image::new(self.rot_imgs.indicator_mmap_small.none) + .middle_of(state.ids.grid) + .w_h(32.0 * ind_scale, 37.0 * ind_scale) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) + .floating(true) + .parent(ui.window) + .set(state.ids.indicator, ui); } else { Image::new(self.imgs.mmap_frame_closed) .w_h(100.0 * 2.0, 11.0 * 2.0) @@ -186,9 +222,9 @@ impl<'a> Widget for MiniMap<'a> { self.imgs.mmap_closed }) .wh(if self.show.mini_map { - [100.0 * 0.4; 2] + [100.0 * 0.3; 2] } else { - [100.0 * 0.2 * zoom; 2] + [100.0 * 0.2; 2] }) .hover_image(if self.show.mini_map { self.imgs.mmap_open_hover @@ -240,20 +276,20 @@ impl<'a> Widget for MiniMap<'a> { // Region Name Text::new(state.last_region_name.as_ref().unwrap_or(&"".to_owned())) .mid_top_with_margin_on(ui.window, 200.0) - .font_size(70) - .font_id(self.fonts.alkhemi) + .font_size(self.fonts.alkhemi.scale(70)) + .font_id(self.fonts.alkhemi.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade)) .set(state.ids.zone_display_bg, ui); Text::new(state.last_region_name.as_ref().unwrap_or(&"".to_owned())) .top_left_with_margins_on(state.ids.zone_display_bg, -2.5, -2.5) - .font_size(70) - .font_id(self.fonts.alkhemi) + .font_size(self.fonts.alkhemi.scale(70)) + .font_id(self.fonts.alkhemi.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade)) .set(state.ids.zone_display, ui); }, None => Text::new(" ") .middle_of(ui.window) - .font_size(14) + .font_size(self.fonts.alkhemi.scale(14)) .color(HP_COLOR) .set(state.ids.zone_display, ui), } @@ -267,13 +303,17 @@ impl<'a> Widget for MiniMap<'a> { state.ids.mmap_frame, if self.show.mini_map { 6.0 } else { 0.0 }, ) - .font_size(if self.show.mini_map { 30 } else { 18 }) - .font_id(self.fonts.cyri) + .font_size( + self.fonts + .cyri + .scale(if self.show.mini_map { 20 } else { 18 }), + ) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.mmap_location, ui), None => Text::new(" ") .mid_top_with_margin_on(state.ids.mmap_frame, 0.0) - .font_size(18) + .font_size(self.fonts.cyri.scale(18)) .color(TEXT_COLOR) .set(state.ids.mmap_location, ui), } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 1a16e2c2d8..760bd4a2ef 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -13,7 +13,7 @@ mod skillbar; mod social; mod spell; -use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot}; +use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot, ui::img_ids::Rotations}; pub use settings_window::ScaleChange; use std::time::Duration; @@ -39,14 +39,13 @@ use crate::{ i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, Consts, FluidMode, Globals, Renderer}, scene::camera::Camera, - ui::{Graphic, Ingameable, ScaleMode, Ui}, + ui::{fonts::ConrodVoxygenFonts, Graphic, Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, GameInput}, GlobalState, }; use client::{Client, Event as ClientEvent}; use common::{assets::load_expect, comp, terrain::TerrainChunk, vol::RectRasterableVol}; use conrod_core::{ - image::Id, text::cursor::Index, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, @@ -179,16 +178,6 @@ widget_ids! { } } -font_ids! { - pub struct Fonts { - opensans: "voxygen.font.OpenSans-Regular", - metamorph: "voxygen.font.Metamorphous-Regular", - alkhemi: "voxygen.font.Alkhemikal", - wizard: "voxygen.font.wizard", - cyri:"voxygen.font.haxrcorp_4089_cyrillic_altgr", - } -} - pub struct DebugInfo { pub tps: f64, pub ping_ms: f64, @@ -212,6 +201,7 @@ pub enum Event { ChangeAudioDevice(String), ChangeMaxFPS(u32), ChangeFOV(u16), + ChangeGamma(f32), AdjustWindowSize([u16; 2]), ToggleFullscreen, ChangeAaMode(AaMode), @@ -307,7 +297,7 @@ impl Show { fn map(&mut self, open: bool) { self.map = open; self.bag = false; - self.want_grab = !open; + self.want_grab = true; } fn character_window(&mut self, open: bool) { @@ -431,10 +421,10 @@ impl Show { pub struct Hud { ui: Ui, ids: Ids, - world_map: (Id, Vec2), + world_map: (/* Id */ Rotations, Vec2), imgs: Imgs, item_imgs: ItemImgs, - fonts: Fonts, + fonts: ConrodVoxygenFonts, rot_imgs: ImgsRot, new_messages: VecDeque, show: Show, @@ -446,8 +436,8 @@ pub struct Hud { force_chat_input: Option, force_chat_cursor: Option, pulse: f32, - zoom: f32, velocity: f32, + voxygen_i18n: std::sync::Arc, } impl Hud { @@ -461,7 +451,7 @@ impl Hud { let ids = Ids::new(ui.id_generator()); // Load world map let world_map = ( - ui.add_graphic(Graphic::Image(client.world_map.0.clone())), + ui.add_graphic_with_rotations(Graphic::Image(client.world_map.0.clone())), client.world_map.1, ); // Load images. @@ -470,8 +460,13 @@ impl Hud { let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!"); // Load item images. let item_imgs = ItemImgs::new(&mut ui); + // Load language + let voxygen_i18n = load_expect::(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); // Load fonts. - let fonts = Fonts::load(&mut ui).expect("Failed to load fonts!"); + let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) + .expect("Impossible to load fonts!"); Self { ui, @@ -497,7 +492,7 @@ impl Hud { quest: false, spell: false, character_window: false, - mini_map: false, + mini_map: true, settings_tab: SettingsTab::Interface, social_tab: SocialTab::Online, want_grab: true, @@ -509,11 +504,17 @@ impl Hud { force_chat_input: None, force_chat_cursor: None, pulse: 0.0, - zoom: 1.0, velocity: 0.0, + voxygen_i18n, } } + pub fn update_language(&mut self, voxygen_i18n: std::sync::Arc) { + self.voxygen_i18n = voxygen_i18n; + self.fonts = ConrodVoxygenFonts::load(&self.voxygen_i18n.fonts, &mut self.ui) + .expect("Impossible to load fonts!"); + } + fn update_layout( &mut self, client: &Client, @@ -536,10 +537,6 @@ impl Hud { common::util::GIT_VERSION.to_string() ); - let localized_strings = load_expect::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )); - if self.show.ingame { let ecs = client.state().ecs(); let pos = ecs.read_storage::(); @@ -549,6 +546,7 @@ impl Hud { let interpolated = ecs.read_storage::(); let players = ecs.read_storage::(); let scales = ecs.read_storage::(); + let bodies = ecs.read_storage::(); let entities = ecs.entities(); let me = client.entity(); let own_level = stats @@ -612,6 +610,8 @@ impl Hud { // Max amount the sct font size increases when "flashing" const FLASH_MAX: f32 = 25.0; const BARSIZE: f64 = 2.0; + const MANA_BAR_HEIGHT: f64 = BARSIZE * 1.5; + const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0; // Get player position. let player_pos = client .state() @@ -631,22 +631,23 @@ impl Hud { let mut sct_id_walker = self.ids.scts.walk(); // Render Health Bars - for (pos, stats, energy, scale, hp_floater_list) in ( + for (pos, stats, energy, height_offset, hp_floater_list) in ( &entities, &pos, interpolated.maybe(), &stats, &energy, scales.maybe(), + &bodies, &hp_floater_lists, ) .join() - .filter(|(entity, _, _, stats, _, _, _)| { + .filter(|(entity, _, _, stats, _, _, _, _)| { *entity != me && !stats.is_dead //&& stats.health.current() != stats.health.maximum() }) // Don't show outside a certain range - .filter(|(_, pos, _, _, _, _, hpfl)| { + .filter(|(_, pos, _, _, _, _, _, hpfl)| { pos.0.distance_squared(player_pos) < (if hpfl .time_since_last_dmg_by_me @@ -658,12 +659,13 @@ impl Hud { }) .powi(2) }) - .map(|(_, pos, interpolated, stats, energy, scale, f)| { + .map(|(_, pos, interpolated, stats, energy, scale, body, f)| { ( interpolated.map_or(pos.0, |i| i.pos), stats, energy, - scale.map_or(1.0, |s| s.0), + // TODO: when body.height() is more accurate remove the 2.0 + body.height() * 2.0 * scale.map_or(1.0, |s| s.0), f, ) }) @@ -690,21 +692,22 @@ impl Hud { let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); + let ingame_pos = pos + Vec3::unit_z() * height_offset; + // Background - Rectangle::fill_with( - [82.0 * BARSIZE + 1.0, 8.0 * BARSIZE + 1.0], - Color::Rgba(0.1, 0.1, 0.1, 0.9), - ) - .x_y(0.0, -25.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) - .set(back_id, ui_widgets); + Image::new(self.imgs.enemy_health_bg) + .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) + .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) + .color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8))) + .position_ingame(ingame_pos) + .set(back_id, ui_widgets); // % HP Filling Image::new(self.imgs.enemy_bar) - .w_h(72.9 * (hp_percentage / 100.0) * BARSIZE, 5.9 * BARSIZE) + .w_h(73.0 * (hp_percentage / 100.0) * BARSIZE, 6.0 * BARSIZE) .x_y( (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE, - -23.0, + MANA_BAR_Y + 7.5, ) .color(Some(if hp_percentage <= 25.0 { crit_hp_color @@ -713,29 +716,29 @@ impl Hud { } else { HP_COLOR })) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .position_ingame(ingame_pos) .set(health_bar_id, ui_widgets); // % Mana Filling Rectangle::fill_with( [ - 73.0 * (energy.current() as f64 / energy.maximum() as f64) * BARSIZE, - 1.5 * BARSIZE, + 72.0 * (energy.current() as f64 / energy.maximum() as f64) * BARSIZE, + MANA_BAR_HEIGHT, ], MANA_COLOR, ) .x_y( - ((4.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE, - -32.0, + ((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE, + MANA_BAR_Y, //-32.0, ) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .position_ingame(ingame_pos) .set(mana_bar_id, ui_widgets); // Foreground Image::new(self.imgs.enemy_health) .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) - .x_y(0.0, -25.5) + .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99))) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .position_ingame(ingame_pos) .set(front_id, ui_widgets); // Enemy SCT @@ -797,25 +800,27 @@ impl Hud { // Timer sets the widget offset let y = (timer as f64 / crate::ecs::sys::floater::HP_SHOWTIME as f64 * number_speed) - + 30.0; + + 100.0; // Timer sets text transparency let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - timer) * 0.25) + 0.2; Text::new(&format!("{}", (hp_damage).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade)) .x_y(0.0, y - 3.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8)) + .position_ingame(ingame_pos) .set(sct_bg_id, ui_widgets); Text::new(&format!("{}", hp_damage.abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .x_y(0.0, y) .color(if hp_damage < 0 { Color::Rgba(font_col.r, font_col.g, font_col.b, fade) } else { Color::Rgba(0.1, 1.0, 0.1, fade) }) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8)) + .position_ingame(ingame_pos) .set(sct_id, ui_widgets); } else { for floater in floaters { @@ -841,7 +846,7 @@ impl Hud { let y = (floater.timer as f64 / crate::ecs::sys::floater::HP_SHOWTIME as f64 * number_speed) - + 30.0; + + 100.0; // Timer sets text transparency let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - floater.timer) * 0.25) @@ -849,27 +854,25 @@ impl Hud { Text::new(&format!("{}", (floater.hp_change).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .color(if floater.hp_change < 0 { Color::Rgba(0.0, 0.0, 0.0, fade) } else { Color::Rgba(0.1, 1.0, 0.1, 0.0) }) .x_y(0.0, y - 3.0) - .position_ingame( - pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8), - ) + .position_ingame(ingame_pos) .set(sct_bg_id, ui_widgets); Text::new(&format!("{}", (floater.hp_change).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .x_y(0.0, y) .color(if floater.hp_change < 0 { Color::Rgba(font_col.r, font_col.g, font_col.b, fade) } else { Color::Rgba(0.1, 1.0, 0.1, 0.0) }) - .position_ingame( - pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8), - ) + .position_ingame(ingame_pos) .set(sct_id, ui_widgets); } } @@ -920,6 +923,7 @@ impl Hud { ((crate::ecs::sys::floater::MY_HP_SHOWTIME - timer) * 0.25) + 0.2; Text::new(&format!("{}", (hp_damage).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .color(if hp_damage < 0 { Color::Rgba(0.0, 0.0, 0.0, hp_fade) } else { @@ -929,6 +933,7 @@ impl Hud { .set(player_sct_bg_id, ui_widgets); Text::new(&format!("{}", (hp_damage).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .color(if hp_damage < 0 { Color::Rgba(1.0, 0.1, 0.0, hp_fade) } else { @@ -992,11 +997,13 @@ impl Hud { + 0.2; Text::new(&format!("{}", (floater.hp_change).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, hp_fade)) .x_y(x, y - 3.0) .set(player_sct_bg_id, ui_widgets); Text::new(&format!("{}", (floater.hp_change).abs())) .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) .color(if floater.hp_change < 0 { Color::Rgba(1.0, 0.1, 0.0, hp_fade) } else { @@ -1047,6 +1054,7 @@ impl Hud { Text::new(&format!("{} Exp", exp_change)) .font_size(font_size_xp) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade)) .x_y( ui_widgets.win_w * (0.5 * rand.0 as f64 - 0.25), @@ -1055,6 +1063,7 @@ impl Hud { .set(player_sct_bg_id, ui_widgets); Text::new(&format!("{} Exp", exp_change)) .font_size(font_size_xp) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.59, 0.41, 0.67, fade)) .x_y( ui_widgets.win_w * (0.5 * rand.0 as f64 - 0.25), @@ -1088,6 +1097,7 @@ impl Hud { Text::new(&format!("{} Exp", floater.exp_change)) .font_size(font_size_xp) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade)) .x_y( ui_widgets.win_w * (0.5 * floater.rand.0 as f64 - 0.25), @@ -1096,6 +1106,7 @@ impl Hud { .set(player_sct_bg_id, ui_widgets); Text::new(&format!("{} Exp", floater.exp_change)) .font_size(font_size_xp) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.59, 0.41, 0.67, fade)) .x_y( ui_widgets.win_w * (0.5 * floater.rand.0 as f64 - 0.25), @@ -1108,19 +1119,20 @@ impl Hud { } // Render Name Tags - for (pos, name, level, scale) in ( + for (pos, name, level, height_offset) in ( &entities, &pos, interpolated.maybe(), &stats, players.maybe(), scales.maybe(), + &bodies, &hp_floater_lists, ) .join() - .filter(|(entity, _, _, stats, _, _, _)| *entity != me && !stats.is_dead) + .filter(|(entity, _, _, stats, _, _, _, _)| *entity != me && !stats.is_dead) // Don't show outside a certain range - .filter(|(_, pos, _, _, _, _, hpfl)| { + .filter(|(_, pos, _, _, _, _, _, hpfl)| { pos.0.distance_squared(player_pos) < (if hpfl .time_since_last_dmg_by_me @@ -1132,7 +1144,7 @@ impl Hud { }) .powi(2) }) - .map(|(_, pos, interpolated, stats, player, scale, _)| { + .map(|(_, pos, interpolated, stats, player, scale, body, _)| { // TODO: This is temporary // If the player used the default character name display their name instead let name = if stats.name == "Character Name" { @@ -1144,7 +1156,7 @@ impl Hud { interpolated.map_or(pos.0, |i| i.pos), format!("{}", name), stats.level, - scale.map_or(1.0, |s| s.0), + body.height() * 2.0 * scale.map_or(1.0, |s| s.0), ) }) { @@ -1163,18 +1175,22 @@ impl Hud { &mut ui_widgets.widget_id_generator(), ); + let ingame_pos = pos + Vec3::unit_z() * height_offset; + // Name Text::new(&name) + .font_id(self.fonts.cyri.conrod_id) .font_size(30) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .x_y(-1.0, 16.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .x_y(-1.0, MANA_BAR_Y + 48.0) + .position_ingame(ingame_pos) .set(name_bg_id, ui_widgets); Text::new(&name) + .font_id(self.fonts.cyri.conrod_id) .font_size(30) .color(Color::Rgba(0.61, 0.61, 0.89, 1.0)) - .x_y(0.0, 18.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .x_y(0.0, MANA_BAR_Y + 50.0) + .position_ingame(ingame_pos) .set(name_id, ui_widgets); // Level @@ -1191,6 +1207,7 @@ impl Hud { // -5 - +5 levels around player level -> equal // - 5 levels below player -> low Text::new(if level_comp < 10 { &level_str } else { "?" }) + .font_id(self.fonts.cyri.conrod_id) .font_size(if op_level > 9 && level_comp < 10 { 14 } else { @@ -1203,8 +1220,8 @@ impl Hud { } else { EQUAL }) - .x_y(-37.0 * BARSIZE, -23.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .x_y(-37.0 * BARSIZE, MANA_BAR_Y + 9.0) + .position_ingame(ingame_pos) .set(level_id, ui_widgets); if level_comp > 9 { let skull_ani = ((self.pulse * 0.7/* speed factor */).cos() * 0.5 + 0.5) * 10.0; //Animation timer @@ -1214,16 +1231,16 @@ impl Hud { self.imgs.skull }) .w_h(18.0 * BARSIZE, 18.0 * BARSIZE) - .x_y(-39.0 * BARSIZE, -25.0) + .x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5)) + .position_ingame(ingame_pos) .set(level_skull_id, ui_widgets); } } } // Introduction Text - let intro_text = &localized_strings.get("hud.welcome"); + let intro_text = &self.voxygen_i18n.get("hud.welcome"); if self.show.intro && !self.show.esc_menu && !self.intro_2 { match global_state.settings.gameplay.intro_show { Intro::Show => { @@ -1233,15 +1250,16 @@ impl Hud { .set(self.ids.intro_bg, ui_widgets); Text::new(intro_text) .top_left_with_margins_on(self.ids.intro_bg, 10.0, 10.0) - .font_size(20) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(20)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.intro_text, ui_widgets); if Button::image(self.imgs.button) .w_h(100.0, 50.0) .mid_bottom_with_margin_on(self.ids.intro_bg, 10.0) - .label(&localized_strings.get("common.close")) - .label_font_size(20) + .label(&self.voxygen_i18n.get("common.close")) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) @@ -1277,10 +1295,10 @@ impl Hud { { self.never_show = !self.never_show }; - Text::new(&localized_strings.get("hud.do_not_show_on_startup")) + Text::new(&self.voxygen_i18n.get("hud.do_not_show_on_startup")) .right_from(self.ids.intro_check, 10.0) - .font_size(10) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(10)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.intro_check_text, ui_widgets); // X-button @@ -1316,15 +1334,16 @@ impl Hud { .set(self.ids.intro_bg, ui_widgets); Text::new(intro_text) .top_left_with_margins_on(self.ids.intro_bg, 10.0, 10.0) - .font_size(20) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(20)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.intro_text, ui_widgets); if Button::image(self.imgs.button) .w_h(100.0, 50.0) .mid_bottom_with_margin_on(self.ids.intro_bg, 10.0) - .label(&localized_strings.get("common.close")) - .label_font_size(20) + .label(&self.voxygen_i18n.get("common.close")) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) @@ -1361,23 +1380,23 @@ impl Hud { // Alpha Version Text::new(&version) .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.version, ui_widgets); // Ticks per second Text::new(&format!("FPS: {:.0}", debug_info.tps)) .color(TEXT_COLOR) .down_from(self.ids.version, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.fps_counter, ui_widgets); // Ping Text::new(&format!("Ping: {:.0}ms", debug_info.ping_ms)) .color(TEXT_COLOR) .down_from(self.ids.fps_counter, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.ping, ui_widgets); // Player's position let coordinates_text = match debug_info.coordinates { @@ -1390,8 +1409,8 @@ impl Hud { Text::new(&coordinates_text) .color(TEXT_COLOR) .down_from(self.ids.ping, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.coordinates, ui_widgets); // Player's velocity let velocity_text = match debug_info.velocity { @@ -1407,8 +1426,8 @@ impl Hud { Text::new(&velocity_text) .color(TEXT_COLOR) .down_from(self.ids.coordinates, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.velocity, ui_widgets); // Loaded distance Text::new(&format!( @@ -1418,8 +1437,8 @@ impl Hud { )) .color(TEXT_COLOR) .down_from(self.ids.velocity, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.loaded_distance, ui_widgets); // Time let time_in_seconds = client.state().get_time_of_day(); @@ -1434,8 +1453,8 @@ impl Hud { )) .color(TEXT_COLOR) .down_from(self.ids.loaded_distance, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.time, ui_widgets); // Number of entities @@ -1443,8 +1462,8 @@ impl Hud { Text::new(&format!("Entity count: {}", entity_count)) .color(TEXT_COLOR) .down_from(self.ids.time, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.entity_count, ui_widgets); // Number of chunks @@ -1454,8 +1473,8 @@ impl Hud { )) .color(TEXT_COLOR) .down_from(self.ids.entity_count, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.num_chunks, ui_widgets); // Number of figures @@ -1465,13 +1484,14 @@ impl Hud { )) .color(TEXT_COLOR) .down_from(self.ids.num_chunks, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.num_figures, ui_widgets); // Help Window Text::new( - &localized_strings + &self + .voxygen_i18n .get("hud.press_key_to_toggle_keybindings_fmt") .replace( "{key}", @@ -1480,12 +1500,13 @@ impl Hud { ) .color(TEXT_COLOR) .down_from(self.ids.num_figures, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.help_info, ui_widgets); // Info about Debug Shortcut Text::new( - &localized_strings + &self + .voxygen_i18n .get("hud.press_key_to_toggle_debug_info_fmt") .replace( "{key}", @@ -1494,13 +1515,14 @@ impl Hud { ) .color(TEXT_COLOR) .down_from(self.ids.help_info, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.debug_info, ui_widgets); } else { // Help Window Text::new( - &localized_strings + &self + .voxygen_i18n .get("hud.press_key_to_show_keybindings_fmt") .replace( "{key}", @@ -1509,12 +1531,13 @@ impl Hud { ) .color(TEXT_COLOR) .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) - .font_id(self.fonts.cyri) - .font_size(16) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(16)) .set(self.ids.help_info, ui_widgets); // Info about Debug Shortcut Text::new( - &localized_strings + &self + .voxygen_i18n .get("hud.press_key_to_show_debug_info_fmt") .replace( "{key}", @@ -1523,8 +1546,8 @@ impl Hud { ) .color(TEXT_COLOR) .down_from(self.ids.help_info, 5.0) - .font_id(self.fonts.cyri) - .font_size(12) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(12)) .set(self.ids.debug_info, ui_widgets); } @@ -1539,8 +1562,9 @@ impl Hud { .w_h(120.0, 50.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("hud.show_tips")) - .label_font_size(20) + .label(&self.voxygen_i18n.get("hud.show_tips")) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .mid_bottom_with_margin_on(self.ids.help, 20.0) .set(self.ids.button_help3, ui_widgets) @@ -1590,10 +1614,9 @@ impl Hud { &self.show, client, &self.imgs, - self.world_map, + &self.rot_imgs, + &self.world_map, &self.fonts, - self.pulse, - self.zoom, ) .set(self.ids.minimap, ui_widgets) { @@ -1685,7 +1708,7 @@ impl Hud { &self.show, &self.imgs, &self.fonts, - &localized_strings, + &self.voxygen_i18n, ) .set(self.ids.settings_window, ui_widgets) { @@ -1757,6 +1780,9 @@ impl Hud { settings_window::Event::AdjustFOV(new_fov) => { events.push(Event::ChangeFOV(new_fov)); }, + settings_window::Event::AdjustGamma(new_gamma) => { + events.push(Event::ChangeGamma(new_gamma)); + }, settings_window::Event::ChangeAaMode(new_aa_mode) => { events.push(Event::ChangeAaMode(new_aa_mode)); }, @@ -1786,7 +1812,7 @@ impl Hud { client, &self.imgs, &self.fonts, - &localized_strings, + &self.voxygen_i18n, ) .set(self.ids.social_window, ui_widgets) { @@ -1809,7 +1835,7 @@ impl Hud { &player_stats, &self.imgs, &self.fonts, - &localized_strings, + &self.voxygen_i18n, ) .set(self.ids.character_window, ui_widgets) { @@ -1828,7 +1854,7 @@ impl Hud { client, &self.imgs, &self.fonts, - &localized_strings, + &self.voxygen_i18n, ) .set(self.ids.spell, ui_widgets) { @@ -1847,7 +1873,7 @@ impl Hud { client, &self.imgs, &self.fonts, - &localized_strings, + &self.voxygen_i18n, ) .set(self.ids.quest, ui_widgets) { @@ -1864,7 +1890,8 @@ impl Hud { &self.show, client, &self.imgs, - self.world_map, + &self.rot_imgs, + &self.world_map, &self.fonts, self.pulse, self.velocity, @@ -1880,7 +1907,7 @@ impl Hud { } if self.show.esc_menu { - match EscMenu::new(&self.imgs, &self.fonts, &localized_strings) + match EscMenu::new(&self.imgs, &self.fonts, &self.voxygen_i18n) .set(self.ids.esc_menu, ui_widgets) { Some(esc_menu::Event::OpenSettings(tab)) => { diff --git a/voxygen/src/hud/quest.rs b/voxygen/src/hud/quest.rs index 094c52629b..795abfd475 100644 --- a/voxygen/src/hud/quest.rs +++ b/voxygen/src/hud/quest.rs @@ -1,5 +1,5 @@ -use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR}; -use crate::i18n::VoxygenLocalization; +use super::{img_ids::Imgs, Show, TEXT_COLOR}; +use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts}; use client::{self, Client}; use conrod_core::{ color, @@ -23,7 +23,7 @@ pub struct Quest<'a> { _client: &'a Client, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -34,7 +34,7 @@ impl<'a> Quest<'a> { show: &'a Show, _client: &'a Client, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, ) -> Self { Self { @@ -98,8 +98,8 @@ impl<'a> Widget for Quest<'a> { // TODO: Use an actual character name. Text::new(&self.localized_strings.get("hud.quests")) .mid_top_with_margin_on(state.quest_frame, 6.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .set(state.quest_title, ui); diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 66f6f56675..1c63978804 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -1,11 +1,11 @@ use super::{ - img_ids::Imgs, BarNumbers, CrosshairType, Fonts, Intro, ShortcutNumbers, Show, XpBar, MENU_BG, + img_ids::Imgs, BarNumbers, CrosshairType, Intro, ShortcutNumbers, Show, XpBar, MENU_BG, TEXT_COLOR, }; use crate::{ i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, FluidMode}, - ui::{ImageSlider, ScaleMode, ToggleButton}, + ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton}, GlobalState, }; use conrod_core::{ @@ -89,6 +89,9 @@ widget_ids! { fov_slider, fov_text, fov_value, + gamma_slider, + gamma_text, + gamma_value, aa_mode_text, aa_mode_list, cloud_mode_text, @@ -155,7 +158,7 @@ pub struct SettingsWindow<'a> { global_state: &'a GlobalState, show: &'a Show, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -166,7 +169,7 @@ impl<'a> SettingsWindow<'a> { global_state: &'a GlobalState, show: &'a Show, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, ) -> Self { Self { @@ -199,6 +202,7 @@ pub enum Event { ToggleMouseYInvert(bool), AdjustViewDistance(u32), AdjustFOV(u16), + AdjustGamma(f32), AdjustWindowSize([u16; 2]), ToggleFullscreen, ChangeAaMode(AaMode), @@ -288,7 +292,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Title Text::new(&self.localized_strings.get("common.settings")) .mid_top_with_margin_on(state.ids.settings_bg, 5.0) - .font_size(14) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.settings_title, ui); @@ -311,7 +316,8 @@ impl<'a> Widget for SettingsWindow<'a> { }) .top_left_with_margins_on(state.ids.settings_l, 8.0 * 4.0, 2.0 * 4.0) .label(&self.localized_strings.get("common.interface")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .set(state.ids.interface, ui) .was_clicked() @@ -328,8 +334,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&self.localized_strings.get("hud.settings.general")) .top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.general_txt, ui); @@ -351,8 +357,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&self.localized_strings.get("hud.settings.help_window")) .right_from(state.ids.button_help, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.button_help) .color(TEXT_COLOR) .set(state.ids.show_help_label, ui); @@ -375,8 +381,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&self.localized_strings.get("hud.settings.debug_info")) .right_from(state.ids.debug_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.debug_button) .color(TEXT_COLOR) .set(state.ids.debug_button_label, ui); @@ -402,8 +408,8 @@ impl<'a> Widget for SettingsWindow<'a> { }; Text::new(&self.localized_strings.get("hud.settings.tips_on_startup")) .right_from(state.ids.tips_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.button_help) .color(TEXT_COLOR) .set(state.ids.tips_button_label, ui); @@ -411,8 +417,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Ui Scale Text::new(&self.localized_strings.get("hud.settings.ui_scale")) .down_from(state.ids.tips_button, 20.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.ui_scale_label, ui); @@ -445,8 +451,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(self.localized_strings.get("hud.settings.relative_scaling")) .right_from(state.ids.relative_to_win_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.relative_to_win_button) .color(TEXT_COLOR) .set(state.ids.relative_to_win_text, ui); @@ -480,8 +486,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(self.localized_strings.get("hud.settings.custom_scaling")) .right_from(state.ids.absolute_scale_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.absolute_scale_button) .color(TEXT_COLOR) .set(state.ids.absolute_scale_text, ui); @@ -507,8 +513,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Custom Scaling Text Text::new(&format!("{:.2}", scale)) .right_from(state.ids.ui_scale_slider, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.ui_scale_value, ui); } else { @@ -657,14 +663,14 @@ impl<'a> Widget for SettingsWindow<'a> { // Crosshair Transparency Text and Slider Text::new(&self.localized_strings.get("hud.settings.crosshair")) .down_from(state.ids.absolute_scale_button, 20.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.ch_title, ui); Text::new(&self.localized_strings.get("hud.settings.transparency")) .right_from(state.ids.ch_3_bg, 20.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.ch_transp_text, ui); @@ -687,17 +693,17 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&format!("{:.2}", crosshair_transp,)) .right_from(state.ids.ch_transp_slider, 8.0) - .font_size(14) + .font_size(self.fonts.cyri.scale(14)) .graphics_for(state.ids.ch_transp_slider) - .font_id(self.fonts.cyri) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.ch_transp_value, ui); // Hotbar text Text::new(&self.localized_strings.get("hud.settings.hotbar")) .down_from(state.ids.ch_1_bg, 20.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.hotbar_title, ui); // Show xp bar @@ -729,8 +735,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.toggle_bar_experience"), ) .right_from(state.ids.show_xpbar_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.show_xpbar_button) .color(TEXT_COLOR) .set(state.ids.show_xpbar_text, ui); @@ -763,8 +769,8 @@ impl<'a> Widget for SettingsWindow<'a> { } Text::new(&self.localized_strings.get("hud.settings.toggle_shortcuts")) .right_from(state.ids.show_shortcuts_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.show_shortcuts_button) .color(TEXT_COLOR) .set(state.ids.show_shortcuts_text, ui); @@ -792,8 +798,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.scrolling_combat_text"), ) .top_left_with_margins_on(state.ids.settings_content_r, 5.0, 5.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.sct_title, ui); // Generally toggle the SCT @@ -817,8 +823,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.scrolling_combat_text"), ) .right_from(state.ids.sct_show_radio, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.sct_show_radio) .color(TEXT_COLOR) .set(state.ids.sct_show_text, ui); @@ -841,8 +847,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.single_damage_number"), ) .right_from(state.ids.sct_single_dmg_radio, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.sct_single_dmg_radio) .color(TEXT_COLOR) .set(state.ids.sct_single_dmg_text, ui); @@ -865,8 +871,8 @@ impl<'a> Widget for SettingsWindow<'a> { } Text::new(&self.localized_strings.get("hud.settings.cumulated_damage")) .right_from(state.ids.sct_show_batch_radio, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.sct_batched_dmg_radio) .color(TEXT_COLOR) .set(state.ids.sct_show_batch_text, ui); @@ -884,8 +890,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&self.localized_strings.get("hud.settings.incoming_damage")) .right_from(state.ids.sct_inc_dmg_radio, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.sct_inc_dmg_radio) .color(TEXT_COLOR) .set(state.ids.sct_inc_dmg_text, ui); @@ -912,8 +918,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.cumulated_incoming_damage"), ) .right_from(state.ids.sct_batch_inc_radio, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.sct_batch_inc_radio) .color(TEXT_COLOR) .set(state.ids.sct_batch_inc_text, ui); @@ -930,8 +936,8 @@ impl<'a> Widget for SettingsWindow<'a> { }, 20.0, ) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.bar_numbers_title, ui); @@ -960,8 +966,8 @@ impl<'a> Widget for SettingsWindow<'a> { } Text::new(&self.localized_strings.get("hud.settings.none")) .right_from(state.ids.show_bar_numbers_none_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.show_bar_numbers_none_button) .color(TEXT_COLOR) .set(state.ids.show_bar_numbers_none_text, ui); @@ -991,8 +997,8 @@ impl<'a> Widget for SettingsWindow<'a> { } Text::new(&self.localized_strings.get("hud.settings.values")) .right_from(state.ids.show_bar_numbers_values_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.show_bar_numbers_values_button) .color(TEXT_COLOR) .set(state.ids.show_bar_numbers_values_text, ui); @@ -1022,8 +1028,8 @@ impl<'a> Widget for SettingsWindow<'a> { } Text::new(&self.localized_strings.get("hud.settings.percentages")) .right_from(state.ids.show_bar_numbers_percentage_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.show_bar_numbers_percentage_button) .color(TEXT_COLOR) .set(state.ids.show_bar_numbers_percentage_text, ui); @@ -1031,8 +1037,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Chat Transp Text::new(&self.localized_strings.get("hud.settings.chat")) .down_from(state.ids.show_bar_numbers_percentage_button, 20.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.chat_transp_title, ui); Text::new( @@ -1041,8 +1047,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.background_transparency"), ) .right_from(state.ids.chat_transp_slider, 20.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.chat_transp_text, ui); @@ -1065,8 +1071,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&self.localized_strings.get("common.languages")) .down_from(state.ids.chat_transp_slider, 20.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.language_text, ui); @@ -1085,7 +1091,7 @@ impl<'a> Widget for SettingsWindow<'a> { .w_h(200.0, 22.0) .color(MENU_BG) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.languages_list, ui) { events.push(Event::ChangeLanguage(language_list[clicked].to_owned())); @@ -1111,7 +1117,8 @@ impl<'a> Widget for SettingsWindow<'a> { }) .right_from(state.ids.interface, 0.0) .label(&self.localized_strings.get("common.gameplay")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .set(state.ids.gameplay, ui) .was_clicked() @@ -1127,8 +1134,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Mouse Pan Sensitivity Text::new(&self.localized_strings.get("hud.settings.pan_sensitivity")) .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.mouse_pan_label, ui); @@ -1151,16 +1158,16 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&format!("{}", display_pan)) .right_from(state.ids.mouse_pan_slider, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.mouse_pan_value, ui); // Mouse Zoom Sensitivity Text::new(&self.localized_strings.get("hud.settings.zoom_sensitivity")) .down_from(state.ids.mouse_pan_slider, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.mouse_zoom_label, ui); @@ -1183,8 +1190,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&format!("{}", display_zoom)) .right_from(state.ids.mouse_zoom_slider, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.mouse_zoom_value, ui); @@ -1212,8 +1219,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.invert_scroll_zoom"), ) .right_from(state.ids.mouse_zoom_invert_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.mouse_zoom_invert_button) .color(TEXT_COLOR) .set(state.ids.mouse_zoom_invert_label, ui); @@ -1242,8 +1249,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.invert_mouse_y_axis"), ) .right_from(state.ids.mouse_y_invert_button, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .graphics_for(state.ids.mouse_y_invert_button) .color(TEXT_COLOR) .set(state.ids.mouse_y_invert_label, ui); @@ -1268,7 +1275,8 @@ impl<'a> Widget for SettingsWindow<'a> { }) .right_from(state.ids.gameplay, 0.0) .label(&self.localized_strings.get("common.controls")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .set(state.ids.controls, ui) .was_clicked() @@ -1282,8 +1290,8 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&self.localized_strings.get("hud.settings.control_names")) .color(TEXT_COLOR) .top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0) - .font_id(self.fonts.cyri) - .font_size(18) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)) .set(state.ids.controls_text, ui); // TODO: Replace with buttons that show actual keybinds and allow the user to // change them. @@ -1412,8 +1420,8 @@ impl<'a> Widget for SettingsWindow<'a> { )) .color(TEXT_COLOR) .right_from(state.ids.controls_text, 0.0) - .font_id(self.fonts.cyri) - .font_size(18) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)) .set(state.ids.controls_controls, ui); } @@ -1437,7 +1445,8 @@ impl<'a> Widget for SettingsWindow<'a> { .right_from(state.ids.controls, 0.0) .label(&self.localized_strings.get("common.video")) .parent(state.ids.settings_r) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .set(state.ids.video, ui) .was_clicked() @@ -1450,8 +1459,8 @@ impl<'a> Widget for SettingsWindow<'a> { // View Distance Text::new(&self.localized_strings.get("hud.settings.view_distance")) .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.vd_text, ui); @@ -1477,16 +1486,16 @@ impl<'a> Widget for SettingsWindow<'a> { self.global_state.settings.graphics.view_distance )) .right_from(state.ids.vd_slider, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.vd_value, ui); // Max FPS Text::new(&self.localized_strings.get("hud.settings.maximum_fps")) .down_from(state.ids.vd_slider, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.max_fps_text, ui); @@ -1512,16 +1521,16 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&format!("{}", self.global_state.settings.graphics.max_fps)) .right_from(state.ids.max_fps_slider, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.max_fps_value, ui); // FOV Text::new(&self.localized_strings.get("hud.settings.fov")) .down_from(state.ids.max_fps_slider, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.fov_text, ui); @@ -1544,16 +1553,48 @@ impl<'a> Widget for SettingsWindow<'a> { Text::new(&format!("{}", self.global_state.settings.graphics.fov)) .right_from(state.ids.fov_slider, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.fov_value, ui); + // Gamma + Text::new(&self.localized_strings.get("hud.settings.gamma")) + .down_from(state.ids.fov_slider, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.gamma_text, ui); + + if let Some(new_val) = ImageSlider::discrete( + (self.global_state.settings.graphics.gamma.log2() * 8.0).round() as i32, + 8, + -8, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(104.0, 22.0) + .down_from(state.ids.gamma_text, 8.0) + .track_breadth(12.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.gamma_slider, ui) + { + events.push(Event::AdjustGamma(2.0f32.powf(new_val as f32 / 8.0))); + } + + Text::new(&format!("{}", self.global_state.settings.graphics.gamma)) + .right_from(state.ids.gamma_slider, 8.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.gamma_value, ui); + // AaMode Text::new(&self.localized_strings.get("hud.settings.antialiasing_mode")) - .down_from(state.ids.fov_slider, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .down_from(state.ids.gamma_slider, 8.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.aa_mode_text, ui); @@ -1583,7 +1624,7 @@ impl<'a> Widget for SettingsWindow<'a> { .w_h(400.0, 22.0) .color(MENU_BG) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .down_from(state.ids.aa_mode_text, 8.0) .set(state.ids.aa_mode_list, ui) { @@ -1597,8 +1638,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.cloud_rendering_mode"), ) .down_from(state.ids.aa_mode_list, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.cloud_mode_text, ui); @@ -1619,7 +1660,7 @@ impl<'a> Widget for SettingsWindow<'a> { .w_h(400.0, 22.0) .color(MENU_BG) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .down_from(state.ids.cloud_mode_text, 8.0) .set(state.ids.cloud_mode_list, ui) { @@ -1633,8 +1674,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.fluid_rendering_mode"), ) .down_from(state.ids.cloud_mode_list, 8.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.fluid_mode_text, ui); @@ -1657,7 +1698,7 @@ impl<'a> Widget for SettingsWindow<'a> { .w_h(400.0, 22.0) .color(MENU_BG) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .down_from(state.ids.fluid_mode_text, 8.0) .set(state.ids.fluid_mode_list, ui) { @@ -1666,8 +1707,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Fullscreen Text::new(&self.localized_strings.get("hud.settings.fullscreen")) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .down_from(state.ids.fluid_mode_list, 8.0) .color(TEXT_COLOR) .set(state.ids.fullscreen_label, ui); @@ -1694,9 +1735,9 @@ impl<'a> Widget for SettingsWindow<'a> { .press_image(self.imgs.settings_button_press) .down_from(state.ids.fullscreen_label, 12.0) .label(&self.localized_strings.get("hud.settings.save_window_size")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .set(state.ids.save_window_size_button, ui) .was_clicked() { @@ -1730,7 +1771,8 @@ impl<'a> Widget for SettingsWindow<'a> { .right_from(state.ids.video, 0.0) .parent(state.ids.settings_r) .label(&self.localized_strings.get("common.sound")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .set(state.ids.sound, ui) .was_clicked() @@ -1743,8 +1785,8 @@ impl<'a> Widget for SettingsWindow<'a> { // Music Volume ----------------------------------------------------- Text::new(&self.localized_strings.get("hud.settings.music_volume")) .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.audio_volume_text, ui); @@ -1772,8 +1814,8 @@ impl<'a> Widget for SettingsWindow<'a> { .get("hud.settings.sound_effect_volume"), ) .down_from(state.ids.audio_volume_slider, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.sfx_volume_text, ui); @@ -1799,8 +1841,8 @@ impl<'a> Widget for SettingsWindow<'a> { let device_list = &self.global_state.audio.device_list; Text::new(&self.localized_strings.get("hud.settings.audio_device")) .down_from(state.ids.sfx_volume_slider, 10.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.audio_device_text, ui); @@ -1811,7 +1853,7 @@ impl<'a> Widget for SettingsWindow<'a> { .w_h(400.0, 22.0) .color(MENU_BG) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.opensans) + .label_font_id(self.fonts.opensans.conrod_id) .down_from(state.ids.audio_device_text, 10.0) .set(state.ids.audio_device_list, ui) { diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 65a02c760d..1ed37221d5 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1,9 +1,10 @@ use super::{ - img_ids::Imgs, BarNumbers, Fonts, ShortcutNumbers, XpBar, CRITICAL_HP_COLOR, HP_COLOR, - LOW_HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR, + img_ids::Imgs, BarNumbers, ShortcutNumbers, XpBar, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR, + MANA_COLOR, TEXT_COLOR, XP_COLOR, }; use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, + ui::fonts::ConrodVoxygenFonts, GlobalState, }; use common::{ @@ -108,7 +109,7 @@ pub enum ResourceType { pub struct Skillbar<'a> { global_state: &'a GlobalState, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, stats: &'a Stats, energy: &'a Energy, character_state: &'a CharacterState, @@ -123,7 +124,7 @@ impl<'a> Skillbar<'a> { pub fn new( global_state: &'a GlobalState, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, stats: &'a Stats, energy: &'a Energy, character_state: &'a CharacterState, @@ -264,14 +265,14 @@ impl<'a> Widget for Skillbar<'a> { .replace("{level_nb}", &self.stats.level.level().to_string()); Text::new(&level_up_text) .middle_of(state.ids.level_align) - .font_size(30) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(30)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade_level)) .set(state.ids.level_message_bg, ui); Text::new(&level_up_text) .bottom_left_with_margins_on(state.ids.level_message_bg, 2.0, 2.0) - .font_size(30) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(30)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_level)) .set(state.ids.level_message, ui); Image::new(self.imgs.level_up) @@ -290,8 +291,8 @@ impl<'a> Widget for Skillbar<'a> { if self.stats.is_dead { Text::new(&localized_strings.get("hud.you_died")) .middle_of(ui.window) - .font_size(50) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(50)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.death_message_1_bg, ui); Text::new(&localized_strings.get("hud.press_key_to_respawn").replace( @@ -299,14 +300,14 @@ impl<'a> Widget for Skillbar<'a> { &format!("{:?}", self.global_state.settings.controls.respawn), )) .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0) - .font_size(30) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(30)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.death_message_2_bg, ui); Text::new(&localized_strings.get("hud.you_died")) .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0) - .font_size(50) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(50)) + .font_id(self.fonts.cyri.conrod_id) .color(CRITICAL_HP_COLOR) .set(state.ids.death_message_1, ui); Text::new(&localized_strings.get("hud.press_key_to_respawn").replace( @@ -314,7 +315,8 @@ impl<'a> Widget for Skillbar<'a> { &format!("{:?}", self.global_state.settings.controls.respawn), )) .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0) - .font_size(30) + .font_size(self.fonts.cyri.scale(30)) + .font_id(self.fonts.cyri.conrod_id) .color(CRITICAL_HP_COLOR) .set(state.ids.death_message_2, ui); } @@ -347,8 +349,8 @@ impl<'a> Widget for Skillbar<'a> { 3.5 * scale, 4.0 * scale, ) - .font_size(10) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(10)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .set(state.ids.level_text, ui); Text::new(&next_level) @@ -357,8 +359,8 @@ impl<'a> Widget for Skillbar<'a> { 3.5 * scale, 4.0 * scale, ) - .font_size(10) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(10)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .set(state.ids.next_level_text, ui); } else if self.stats.level.level() < 100 { @@ -369,8 +371,8 @@ impl<'a> Widget for Skillbar<'a> { 3.5 * scale, 3.0 * scale, ) - .font_size(9) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(9)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .set(state.ids.level_text, ui); Text::new(&next_level) @@ -379,8 +381,8 @@ impl<'a> Widget for Skillbar<'a> { 3.5 * scale, 3.0 * scale, ) - .font_size(9) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(9)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .set(state.ids.next_level_text, ui); } else { @@ -391,8 +393,8 @@ impl<'a> Widget for Skillbar<'a> { 3.5 * scale, 2.5 * scale, ) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .set(state.ids.level_text, ui); Text::new(&next_level) @@ -401,8 +403,8 @@ impl<'a> Widget for Skillbar<'a> { 3.5 * scale, 2.5 * scale, ) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .set(state.ids.next_level_text, ui); } @@ -472,8 +474,8 @@ impl<'a> Widget for Skillbar<'a> { 3.0 * scale * 1.5, 4.0 * scale * 1.5, ) - .font_size(17) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(17)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) .set(state.ids.level_text, ui); Text::new(&next_level) @@ -482,8 +484,8 @@ impl<'a> Widget for Skillbar<'a> { 3.0 * scale * 1.5, 4.0 * scale * 1.5, ) - .font_size(15) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(15)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) .set(state.ids.next_level_text, ui); } else if self.stats.level.level() < 100 { @@ -494,8 +496,8 @@ impl<'a> Widget for Skillbar<'a> { 3.0 * scale * 1.5, 3.0 * scale * 1.5, ) - .font_size(15) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(15)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) .set(state.ids.level_text, ui); Text::new(&next_level) @@ -504,8 +506,8 @@ impl<'a> Widget for Skillbar<'a> { 3.0 * scale * 1.5, 3.0 * scale * 1.5, ) - .font_size(15) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(15)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) .set(state.ids.next_level_text, ui); } else { @@ -516,8 +518,8 @@ impl<'a> Widget for Skillbar<'a> { 3.0 * scale * 1.5, 2.75 * scale * 1.5, ) - .font_size(12) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(12)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) .set(state.ids.level_text, ui); Text::new(&next_level) @@ -526,8 +528,8 @@ impl<'a> Widget for Skillbar<'a> { 3.0 * scale * 1.5, 2.75 * scale * 1.5, ) - .font_size(12) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(12)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) .set(state.ids.next_level_text, ui); } @@ -851,74 +853,74 @@ impl<'a> Widget for Skillbar<'a> { if let ShortcutNumbers::On = shortcuts { Text::new("1") .top_right_with_margins_on(state.ids.slot1_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot1_text, ui); Text::new("2") .top_right_with_margins_on(state.ids.slot2_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot2_text, ui); Text::new("3") .top_right_with_margins_on(state.ids.slot3_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot3_text, ui); Text::new("4") .top_right_with_margins_on(state.ids.slot4_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot4_text, ui); Text::new("5") .top_right_with_margins_on(state.ids.slot5_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot5_text, ui); Text::new("M1") .top_left_with_margins_on(state.ids.m1_slot, 5.0, 5.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.m1_text, ui); Text::new("M2") .top_right_with_margins_on(state.ids.m2_slot, 5.0, 5.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.m2_text, ui); Text::new("6") .top_left_with_margins_on(state.ids.slot6_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot6_text, ui); Text::new("7") .top_left_with_margins_on(state.ids.slot7_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot7_text, ui); Text::new("8") .top_left_with_margins_on(state.ids.slot8_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot8_text, ui); Text::new("9") .top_left_with_margins_on(state.ids.slot9_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slot9_text, ui); Text::new("Q") .top_left_with_margins_on(state.ids.slotq_bg, 1.0, 1.0) - .font_size(8) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.slotq_text, ui); }; @@ -963,14 +965,14 @@ impl<'a> Widget for Skillbar<'a> { ); Text::new(&hp_text) .mid_top_with_margin_on(state.ids.healthbar_bg, 6.0 * scale) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.health_text_bg, ui); Text::new(&hp_text) .bottom_left_with_margins_on(state.ids.health_text_bg, 2.0, 2.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.health_text, ui); let energy_text = format!( @@ -981,14 +983,14 @@ impl<'a> Widget for Skillbar<'a> { ); Text::new(&energy_text) .mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.energy_text_bg, ui); Text::new(&energy_text) .bottom_left_with_margins_on(state.ids.energy_text_bg, 2.0, 2.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.energy_text, ui); } @@ -997,27 +999,27 @@ impl<'a> Widget for Skillbar<'a> { let hp_text = format!("{}%", hp_percentage as u32); Text::new(&hp_text) .mid_top_with_margin_on(state.ids.healthbar_bg, 6.0 * scale) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.health_text_bg, ui); Text::new(&hp_text) .bottom_left_with_margins_on(state.ids.health_text_bg, 2.0, 2.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.health_text, ui); let energy_text = format!("{}%", energy_percentage as u32); Text::new(&energy_text) .mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.energy_text_bg, ui); Text::new(&energy_text) .bottom_left_with_margins_on(state.ids.energy_text_bg, 2.0, 2.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.energy_text, ui); } diff --git a/voxygen/src/hud/social.rs b/voxygen/src/hud/social.rs index 641172b128..fb57e8523e 100644 --- a/voxygen/src/hud/social.rs +++ b/voxygen/src/hud/social.rs @@ -1,6 +1,6 @@ -use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR, TEXT_COLOR_3}; +use super::{img_ids::Imgs, Show, TEXT_COLOR, TEXT_COLOR_3}; -use crate::i18n::VoxygenLocalization; +use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts}; use client::{self, Client}; use conrod_core::{ color, @@ -39,7 +39,7 @@ pub struct Social<'a> { show: &'a Show, client: &'a Client, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, #[conrod(common_builder)] @@ -51,7 +51,7 @@ impl<'a> Social<'a> { show: &'a Show, client: &'a Client, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, ) -> Self { Self { @@ -115,8 +115,8 @@ impl<'a> Widget for Social<'a> { // Title Text::new(&self.localized_strings.get("hud.social")) .mid_top_with_margin_on(ids.social_frame, 6.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .set(ids.social_title, ui); @@ -160,7 +160,8 @@ impl<'a> Widget for Social<'a> { }) .top_left_with_margins_on(ids.align, 4.0, 0.0) .label(&self.localized_strings.get("hud.social.online")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .parent(ids.frame) .label_color(TEXT_COLOR) .set(ids.online_tab, ui) @@ -191,15 +192,15 @@ impl<'a> Widget for Social<'a> { .replace("{nb_player}", &format!("{:?}", count)), ) .top_left_with_margins_on(ids.content_align, -2.0, 7.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(ids.online_title, ui); for (i, (_, player_alias)) in self.client.player_list.iter().enumerate() { Text::new(player_alias) .down(3.0) - .font_size(15) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(15)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(ids.player_names[i], ui); } @@ -225,7 +226,8 @@ impl<'a> Widget for Social<'a> { }) .right_from(ids.online_tab, 0.0) .label(&self.localized_strings.get("hud.social.friends")) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .parent(ids.frame) .label_color(TEXT_COLOR_3) .set(ids.friends_tab, ui) @@ -239,8 +241,8 @@ impl<'a> Widget for Social<'a> { if let SocialTab::Friends = self.show.social_tab { Text::new(&self.localized_strings.get("hud.social.not_yet_available")) .middle_of(ids.content_align) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR_3) .set(ids.friends_test, ui); } @@ -256,7 +258,8 @@ impl<'a> Widget for Social<'a> { .right_from(ids.friends_tab, 0.0) .label(&self.localized_strings.get("hud.social.faction")) .parent(ids.frame) - .label_font_size(14) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR_3) .set(ids.faction_tab, ui) .was_clicked() @@ -269,8 +272,8 @@ impl<'a> Widget for Social<'a> { if let SocialTab::Faction = self.show.social_tab { Text::new(&self.localized_strings.get("hud.social.not_yet_available")) .middle_of(ids.content_align) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR_3) .set(ids.faction_test, ui); } diff --git a/voxygen/src/hud/spell.rs b/voxygen/src/hud/spell.rs index 24aa2d55ff..ca49c4b30c 100644 --- a/voxygen/src/hud/spell.rs +++ b/voxygen/src/hud/spell.rs @@ -1,4 +1,5 @@ -use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR}; +use super::{img_ids::Imgs, Show, TEXT_COLOR}; +use crate::ui::fonts::ConrodVoxygenFonts; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Text}, @@ -25,7 +26,7 @@ pub struct Spell<'a> { _client: &'a Client, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, #[conrod(common_builder)] @@ -37,7 +38,7 @@ impl<'a> Spell<'a> { show: &'a Show, _client: &'a Client, imgs: &'a Imgs, - fonts: &'a Fonts, + fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc, ) -> Self { Self { @@ -101,8 +102,8 @@ impl<'a> Widget for Spell<'a> { // TODO: Use an actual character name. Text::new(&self.localized_strings.get("hud.spell")) .mid_top_with_margin_on(state.spell_frame, 6.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .set(state.spell_title, ui); diff --git a/voxygen/src/i18n.rs b/voxygen/src/i18n.rs index 9d1e637067..3b8064a849 100644 --- a/voxygen/src/i18n.rs +++ b/voxygen/src/i18n.rs @@ -30,10 +30,26 @@ pub struct LanguageMetadata { pub language_identifier: String, } +/// Store font metadata +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Font { + /// Key to retrieve the font in the asset system + pub asset_key: String, + + /// Scale ratio to resize the UI text dynamicly + pub scale_ratio: f32, +} + +impl Font { + /// Scale input size to final UI size + pub fn scale(&self, value: u32) -> u32 { (value as f32 * self.scale_ratio).round() as u32 } +} + +/// Store font metadata +pub type VoxygenFonts = HashMap; + /// Store internationalization data -/// -/// TODO: store the font locations here (Font asset path for instance) -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VoxygenLocalization { /// A map storing the localized texts /// @@ -44,6 +60,9 @@ pub struct VoxygenLocalization { /// into a ASCII version by using the `deunicode` crate. pub convert_utf8_to_ascii: bool, + /// Font configuration is stored here + pub fonts: VoxygenFonts, + pub metadata: LanguageMetadata, } diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index f3fad20a8b..301aab5d19 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -1,3 +1,6 @@ /// Used by benchmarks pub mod mesh; pub mod render; + +// Used by tests +pub mod i18n; diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index ea6f4ee58c..53536055ce 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -121,7 +121,7 @@ fn main() { }; let mut audio = if settings.audio.audio_on { - AudioFrontend::new(audio_device(), 16) + AudioFrontend::new(audio_device(), settings.audio.max_sfx_channels) } else { AudioFrontend::no_audio() }; diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index a3691c3bea..6dc28fb820 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -99,6 +99,7 @@ impl PlayState for CharSelectionState { global_state.window.renderer_mut(), &self.client.borrow(), humanoid_body.clone(), + global_state.settings.graphics.gamma, ); // Render the scene. diff --git a/voxygen/src/menu/char_selection/scene.rs b/voxygen/src/menu/char_selection/scene.rs index fd9e903693..ef8a5ba82a 100644 --- a/voxygen/src/menu/char_selection/scene.rs +++ b/voxygen/src/menu/char_selection/scene.rs @@ -119,6 +119,7 @@ impl Scene { renderer: &mut Renderer, client: &Client, body: Option, + gamma: f32, ) { self.camera.set_focus_pos(Vec3::unit_z() * 1.5); self.camera.update(client.state().get_time()); @@ -142,6 +143,7 @@ impl Scene { 0, BlockKind::Air, None, + gamma, )]) { error!("Renderer failed to update: {:?}", err); } diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 22ec50d31b..561381b271 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -3,6 +3,7 @@ use crate::{ meta::CharacterData, render::{Consts, Globals, Renderer}, ui::{ + fonts::ConrodVoxygenFonts, img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelSs9Graphic}, ImageFrame, ImageSlider, Tooltip, Tooltipable, Ui, }, @@ -21,6 +22,7 @@ use conrod_core::{ widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Scrollbar, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, }; + const STARTER_HAMMER: &str = "common.items.weapons.starter_hammer"; const STARTER_BOW: &str = "common.items.weapons.starter_bow"; const STARTER_AXE: &str = "common.items.weapons.starter_axe"; @@ -162,10 +164,6 @@ widget_ids! { image_ids! { struct Imgs { - button: "voxygen.element.buttons.button", - button_hover: "voxygen.element.buttons.button_hover", - button_press: "voxygen.element.buttons.button_press", - name_input: "voxygen.element.misc_bg.textbox", charlist_frame: "voxygen.element.frames.window_4", server_frame: "voxygen.element.frames.server_frame", selection: "voxygen.element.frames.selection", @@ -184,6 +182,8 @@ image_ids! { + name_input: "voxygen.element.misc_bg.textbox_mid", + // Tool Icons daggers: "voxygen.element.icons.daggers", sword: "voxygen.element.icons.sword", @@ -213,6 +213,11 @@ image_ids! { icon_border_press: "voxygen.element.buttons.border_press", icon_border_pressed: "voxygen.element.buttons.border_pressed", + + button: "voxygen.element.buttons.button", + button_hover: "voxygen.element.buttons.button_hover", + button_press: "voxygen.element.buttons.button_press", + nothing: (), } @@ -227,16 +232,6 @@ rotation_image_ids! { } } -font_ids! { - pub struct Fonts { - opensans: "voxygen.font.OpenSans-Regular", - metamorph: "voxygen.font.Metamorphous-Regular", - alkhemi: "voxygen.font.Alkhemikal", - cyri:"voxygen.font.haxrcorp_4089_cyrillic_altgr", - wizard: "voxygen.font.wizard", - } -} - pub enum Event { Logout, Play, @@ -265,10 +260,10 @@ pub struct CharSelectionUi { ids: Ids, imgs: Imgs, rot_imgs: ImgsRot, - fonts: Fonts, + fonts: ConrodVoxygenFonts, //character_creation: bool, info_content: InfoContent, - selected_language: String, + voxygen_i18n: std::sync::Arc, //deletion_confirmation: bool, /* pub character_name: String, @@ -290,8 +285,13 @@ impl CharSelectionUi { // Load images let imgs = Imgs::load(&mut ui).expect("Failed to load images!"); let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load images!"); - // Load fonts - let fonts = Fonts::load(&mut ui).expect("Failed to load fonts!"); + // Load language + let voxygen_i18n = load_expect::(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); + // Load fonts. + let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) + .expect("Impossible to load fonts!"); // TODO: Randomize initial values. Self { @@ -301,7 +301,7 @@ impl CharSelectionUi { rot_imgs, fonts, info_content: InfoContent::None, - selected_language: global_state.settings.language.selected_language.clone(), + voxygen_i18n, //deletion_confirmation: false, /* character_creation: false, @@ -334,8 +334,6 @@ impl CharSelectionUi { env!("CARGO_PKG_VERSION"), common::util::GIT_VERSION.to_string() ); - let localized_strings = - load_expect::(&i18n_asset_key(&self.selected_language)); // Tooltip let tooltip_human = Tooltip::new({ @@ -350,9 +348,9 @@ impl CharSelectionUi { 5.0, ) }) - .title_font_size(15) - .desc_font_size(10) - .font_id(self.fonts.cyri) + .title_font_size(self.fonts.cyri.scale(15)) + .desc_font_size(self.fonts.cyri.scale(10)) + .font_id(self.fonts.cyri.conrod_id) .title_text_color(TEXT_COLOR) .desc_text_color(TEXT_COLOR_2); @@ -372,10 +370,10 @@ impl CharSelectionUi { match self.info_content { InfoContent::None => unreachable!(), InfoContent::Deletion(character_index) => { - Text::new(&localized_strings.get("char_selection.delete_permanently")) + Text::new(&self.voxygen_i18n.get("char_selection.delete_permanently")) .mid_top_with_margin_on(self.ids.info_frame, 40.0) - .font_size(24) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(24)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.delete_text, ui_widgets); if Button::image(self.imgs.button) @@ -384,9 +382,9 @@ impl CharSelectionUi { .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label_y(Relative::Scalar(2.0)) - .label(&localized_strings.get("common.no")) - .label_font_id(self.fonts.cyri) - .label_font_size(18) + .label(&self.voxygen_i18n.get("common.no")) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(18)) .label_color(TEXT_COLOR) .set(self.ids.info_no, ui_widgets) .was_clicked() @@ -399,9 +397,9 @@ impl CharSelectionUi { .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label_y(Relative::Scalar(2.0)) - .label(&localized_strings.get("common.yes")) - .label_font_id(self.fonts.cyri) - .label_font_size(18) + .label(&self.voxygen_i18n.get("common.yes")) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(18)) .label_color(TEXT_COLOR) .set(self.ids.info_ok, ui_widgets) .was_clicked() @@ -456,8 +454,8 @@ impl CharSelectionUi { // Server Name Text::new(&client.server_info.name) .mid_top_with_margin_on(self.ids.server_frame_bg, 5.0) - .font_size(26) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(26)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.server_name_text, ui_widgets); //Change Server @@ -467,10 +465,10 @@ impl CharSelectionUi { .parent(self.ids.charlist_bg) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("char_selection.change_server")) + .label(&self.voxygen_i18n.get("char_selection.change_server")) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) - .label_font_size(18) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(18)) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.change_server, ui_widgets) .was_clicked() @@ -480,13 +478,13 @@ impl CharSelectionUi { // Enter World Button let character_count = global_state.meta.characters.len(); - let enter_world_str = &localized_strings.get("char_selection.enter_world"); + let enter_world_str = &self.voxygen_i18n.get("char_selection.enter_world"); let enter_button = Button::image(self.imgs.button) .mid_bottom_with_margin_on(ui_widgets.window, 10.0) .w_h(250.0, 60.0) .label(enter_world_str) - .label_font_size(26) - .label_font_id(self.fonts.cyri) + .label_font_size(self.fonts.cyri.scale(26)) + .label_font_id(self.fonts.cyri.conrod_id) .label_y(conrod_core::position::Relative::Scalar(3.0)); if match &self.mode { @@ -514,10 +512,10 @@ impl CharSelectionUi { .w_h(150.0, 40.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("char_selection.logout")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("char_selection.logout")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.logout_button, ui_widgets) .was_clicked() @@ -528,8 +526,8 @@ impl CharSelectionUi { // Alpha Version Text::new(&version) .top_right_with_margins_on(ui_widgets.window, 5.0, 5.0) - .font_size(14) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.version, ui_widgets); @@ -572,7 +570,7 @@ impl CharSelectionUi { .image_color(Color::Rgba(1.0, 1.0, 1.0, 0.8)) .hover_image(self.imgs.selection_hover) .press_image(self.imgs.selection_press) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .label_y(conrod_core::position::Relative::Scalar(20.0)) .set(self.ids.character_boxes[i], ui_widgets) .was_clicked() @@ -586,7 +584,7 @@ impl CharSelectionUi { .press_image(self.imgs.delete_button_press) .with_tooltip( tooltip_manager, - &localized_strings.get("char_selection.delete_permanently"), + &self.voxygen_i18n.get("char_selection.delete_permanently"), "", &tooltip_human, ) @@ -597,26 +595,27 @@ impl CharSelectionUi { } Text::new(&character.name) .top_left_with_margins_on(self.ids.character_boxes[i], 6.0, 9.0) - .font_size(19) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(19)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.character_names[i], ui_widgets); Text::new( - &localized_strings + &self + .voxygen_i18n .get("char_selection.level_fmt") .replace("{level_nb}", "1"), ) //TODO Insert real level here as soon as they get saved .down_from(self.ids.character_names[i], 4.0) - .font_size(17) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(17)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.character_levels[i], ui_widgets); - Text::new(&localized_strings.get("char_selection.uncanny_valley")) + Text::new(&self.voxygen_i18n.get("char_selection.uncanny_valley")) .down_from(self.ids.character_levels[i], 4.0) - .font_size(17) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(17)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.character_locations[i], ui_widgets); } @@ -637,9 +636,9 @@ impl CharSelectionUi { .w_h(386.0, 80.0) .hover_image(self.imgs.selection_hover) .press_image(self.imgs.selection_press) - .label(&localized_strings.get("char_selection.create_new_charater")) + .label(&self.voxygen_i18n.get("char_selection.create_new_charater")) .label_color(Color::Rgba(0.38, 1.0, 0.07, 1.0)) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .image_color(Color::Rgba(0.38, 1.0, 0.07, 1.0)) .set(self.ids.character_box_2, ui_widgets) .was_clicked() @@ -661,10 +660,10 @@ impl CharSelectionUi { .w_h(150.0, 40.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("common.back")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.back")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.back_button, ui_widgets) .was_clicked() @@ -677,10 +676,12 @@ impl CharSelectionUi { .w_h(150.0, 40.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("common.create")) - .label_font_id(self.fonts.cyri) - .label_color(TEXT_COLOR) - .label_font_size(20) + .label(&self.voxygen_i18n.get("common.create")) + .label_font_id(self.fonts.cyri.conrod_id) + .label_color( + /* if self.mode { TEXT_COLOR } else { */ TEXT_COLOR, /* , } */ + ) + .label_font_size(self.fonts.cyri.scale(20)) .label_y(conrod_core::position::Relative::Scalar(3.0)) .set(self.ids.create_button, ui_widgets) .was_clicked() @@ -705,11 +706,11 @@ impl CharSelectionUi { for event in TextBox::new(name) .w_h(300.0, 60.0) .mid_top_with_margin_on(self.ids.name_input, 2.0) - .font_size(26) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(26)) + .font_id(self.fonts.cyri.conrod_id) .center_justify() .text_color(TEXT_COLOR) - .font_id(self.fonts.cyri) + .font_id(self.fonts.cyri.conrod_id) .color(TRANSPARENT) .border_color(TRANSPARENT) .set(self.ids.name_field, ui_widgets) @@ -745,10 +746,10 @@ impl CharSelectionUi { .set(self.ids.selection_scrollbar, ui_widgets); // Male/Female/Race Icons - Text::new(&localized_strings.get("char_selection.character_creation")) + Text::new(&self.voxygen_i18n.get("char_selection.character_creation")) .mid_top_with_margin_on(self.ids.creation_alignment, 10.0) - .font_size(24) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(24)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.bodyrace_text, ui_widgets); // Alignment @@ -833,7 +834,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.races.human"), + &self.voxygen_i18n.get("common.races.human"), "", &tooltip_human, ) @@ -866,7 +867,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.races.orc"), + &self.voxygen_i18n.get("common.races.orc"), "", &tooltip_human, ) @@ -891,7 +892,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.races.dwarf"), + &self.voxygen_i18n.get("common.races.dwarf"), "", &tooltip_human, ) @@ -916,7 +917,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.races.elf"), + &self.voxygen_i18n.get("common.races.elf"), "", &tooltip_human, ) @@ -942,7 +943,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.races.undead"), + &self.voxygen_i18n.get("common.races.undead"), "", &tooltip_human, ) @@ -967,7 +968,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.races.danari"), + &self.voxygen_i18n.get("common.races.danari"), "", &tooltip_human, ) @@ -993,7 +994,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.weapons.hammer"), + &self.voxygen_i18n.get("common.weapons.hammer"), "", &tooltip_human, ) @@ -1022,7 +1023,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.weapons.bow"), + &self.voxygen_i18n.get("common.weapons.bow"), "", &tooltip_human, ) @@ -1031,10 +1032,6 @@ impl CharSelectionUi { { *tool = Some(STARTER_BOW); } - // REMOVE THIS AFTER IMPLEMENTATION - /*Rectangle::fill_with([67.0, 67.0], color::rgba(0.0, 0.0, 0.0, 0.8)) - .middle_of(self.ids.bow) - .set(self.ids.bow_grey, ui_widgets);*/ // Staff Image::new(self.imgs.staff) .w_h(70.0, 70.0) @@ -1050,7 +1047,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.weapons.staff"), + &self.voxygen_i18n.get("common.weapons.staff"), "", &tooltip_human, ) @@ -1059,10 +1056,6 @@ impl CharSelectionUi { { *tool = Some(STARTER_STAFF); } - // REMOVE THIS AFTER IMPLEMENTATION - /*Rectangle::fill_with([67.0, 67.0], color::rgba(0.0, 0.0, 0.0, 0.8)) - .middle_of(self.ids.staff) - .set(self.ids.staff_grey, ui_widgets);*/ // Sword Image::new(self.imgs.sword) .w_h(70.0, 70.0) @@ -1078,7 +1071,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.weapons.sword"), + &self.voxygen_i18n.get("common.weapons.sword"), "", &tooltip_human, ) @@ -1126,7 +1119,7 @@ impl CharSelectionUi { .press_image(self.imgs.icon_border_press) .with_tooltip( tooltip_manager, - &localized_strings.get("common.weapons.axe"), + &self.voxygen_i18n.get("common.weapons.axe"), "", &tooltip_human, ) @@ -1136,8 +1129,9 @@ impl CharSelectionUi { *tool = Some(STARTER_AXE); } // Sliders - let (metamorph, slider_indicator, slider_range) = ( - self.fonts.cyri, + let (cyri, cyri_size, slider_indicator, slider_range) = ( + self.fonts.cyri.conrod_id, + self.fonts.cyri.scale(18), self.imgs.slider_indicator, self.imgs.slider_range, ); @@ -1151,8 +1145,8 @@ impl CharSelectionUi { Text::new(text) .down_from(prev_id, 22.0) .align_middle_x_of(prev_id) - .font_size(18) - .font_id(metamorph) + .font_size(cyri_size) + .font_id(cyri) .color(TEXT_COLOR) .set(text_id, ui_widgets); ImageSlider::discrete(selected_val, 0, max, slider_indicator, slider_range) @@ -1167,7 +1161,7 @@ impl CharSelectionUi { // Hair Style if let Some(new_val) = char_slider( self.ids.creation_buttons_alignment_2, - localized_strings.get("char_selection.hair_style"), + self.voxygen_i18n.get("char_selection.hair_style"), self.ids.hairstyle_text, body.race.num_hair_styles(body.body_type) as usize - 1, body.hair_style as usize, @@ -1179,7 +1173,7 @@ impl CharSelectionUi { // Hair Color if let Some(new_val) = char_slider( self.ids.hairstyle_slider, - localized_strings.get("char_selection.hair_color"), + self.voxygen_i18n.get("char_selection.hair_color"), self.ids.haircolor_text, body.race.num_hair_colors() as usize - 1, body.hair_color as usize, @@ -1191,7 +1185,7 @@ impl CharSelectionUi { // Skin if let Some(new_val) = char_slider( self.ids.haircolor_slider, - localized_strings.get("char_selection.skin"), + self.voxygen_i18n.get("char_selection.skin"), self.ids.skin_text, body.race.num_skin_colors() as usize - 1, body.skin as usize, @@ -1204,7 +1198,7 @@ impl CharSelectionUi { let current_eyebrows = body.eyebrows; if let Some(new_val) = char_slider( self.ids.skin_slider, - localized_strings.get("char_selection.eyebrows"), + self.voxygen_i18n.get("char_selection.eyebrows"), self.ids.eyebrows_text, humanoid::ALL_EYEBROWS.len() - 1, humanoid::ALL_EYEBROWS @@ -1219,7 +1213,7 @@ impl CharSelectionUi { // EyeColor if let Some(new_val) = char_slider( self.ids.eyebrows_slider, - localized_strings.get("char_selection.eye_color"), + self.voxygen_i18n.get("char_selection.eye_color"), self.ids.eyecolor_text, body.race.num_eye_colors() as usize - 1, body.eye_color as usize, @@ -1232,7 +1226,7 @@ impl CharSelectionUi { let _current_accessory = body.accessory; if let Some(new_val) = char_slider( self.ids.eyecolor_slider, - localized_strings.get("char_selection.accessories"), + self.voxygen_i18n.get("char_selection.accessories"), self.ids.accessories_text, body.race.num_accessories(body.body_type) as usize - 1, body.accessory as usize, @@ -1245,7 +1239,7 @@ impl CharSelectionUi { if body.race.num_beards(body.body_type) > 1 { if let Some(new_val) = char_slider( self.ids.accessories_slider, - localized_strings.get("char_selection.beard"), + self.voxygen_i18n.get("char_selection.beard"), self.ids.beard_text, body.race.num_beards(body.body_type) as usize - 1, body.beard as usize, @@ -1255,10 +1249,10 @@ impl CharSelectionUi { body.beard = new_val as u8; } } else { - Text::new(&localized_strings.get("char_selection.beard")) + Text::new(&self.voxygen_i18n.get("char_selection.beard")) .mid_bottom_with_margin_on(self.ids.accessories_slider, -40.0) - .font_size(18) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR_2) .set(self.ids.beard_text, ui_widgets); ImageSlider::discrete(5, 0, 10, self.imgs.nothing, self.imgs.slider_range) @@ -1275,7 +1269,7 @@ impl CharSelectionUi { let current_chest = body.chest; if let Some(new_val) = char_slider( self.ids.beard_slider, - localized_strings.get("char_selection.chest_color"), + self.voxygen_i18n.get("char_selection.chest_color"), self.ids.chest_text, humanoid::ALL_CHESTS.len() - 1, humanoid::ALL_CHESTS diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 3c05d71eae..9ce56b48dc 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi}; pub struct MainMenuState { main_menu_ui: MainMenuUi, - title_music_channel: Option, singleplayer: Option, } @@ -27,7 +26,6 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), - title_music_channel: None, singleplayer: None, } } @@ -44,11 +42,8 @@ impl PlayState for MainMenuState { let mut client_init: Option = None; // Kick off title music - if self.title_music_channel.is_none() - && global_state.settings.audio.audio_on - && global_state.audio.music_enabled() - { - self.title_music_channel = global_state.audio.play_title_music(); + if global_state.settings.audio.audio_on && global_state.audio.music_enabled() { + global_state.audio.play_title_music(); } // Reset singleplayer server if it was running already diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 3273c9bb96..43d1e774ff 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -3,6 +3,7 @@ use crate::{ render::Renderer, ui::{ self, + fonts::ConrodVoxygenFonts, img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic}, Graphic, ImageFrame, Tooltip, Ui, }, @@ -75,18 +76,24 @@ image_ids! { struct Imgs { v_logo: "voxygen.element.v_logo", - input_bg: "voxygen.element.misc_bg.textbox", - button: "voxygen.element.buttons.button", - button_hover: "voxygen.element.buttons.button_hover", - button_press: "voxygen.element.buttons.button_press", + disclaimer: "voxygen.element.frames.disclaimer", info_frame: "voxygen.element.frames.info_frame_2", banner: "voxygen.element.frames.banner", - banner_top: "voxygen.element.frames.banner_top", + banner_bottom: "voxygen.element.frames.banner_bottom", bg: "voxygen.background.bg_main", + banner_top: "voxygen.element.frames.banner_top", + button: "voxygen.element.buttons.button", + button_hover: "voxygen.element.buttons.button_hover", + button_press: "voxygen.element.buttons.button_press", + input_bg_top: "voxygen.element.misc_bg.textbox_top", + //input_bg_mid: "voxygen.element.misc_bg.textbox_mid", <-- For password input + input_bg_bot: "voxygen.element.misc_bg.textbox_bot", + + nothing: (), @@ -103,16 +110,6 @@ rotation_image_ids! { } } -font_ids! { - pub struct Fonts { - opensans: "voxygen.font.OpenSans-Regular", - metamorph: "voxygen.font.Metamorphous-Regular", - alkhemi: "voxygen.font.Alkhemikal", - cyri:"voxygen.font.haxrcorp_4089_cyrillic_altgr", - wizard: "voxygen.font.wizard", - } -} - pub enum Event { LoginAttempt { username: String, @@ -143,7 +140,6 @@ pub struct MainMenuUi { ids: Ids, imgs: Imgs, rot_imgs: ImgsRot, - fonts: Fonts, username: String, password: String, server_address: String, @@ -154,6 +150,8 @@ pub struct MainMenuUi { show_disclaimer: bool, time: f32, bg_img_id: conrod_core::image::Id, + voxygen_i18n: std::sync::Arc, + fonts: ConrodVoxygenFonts, } impl MainMenuUi { @@ -184,15 +182,19 @@ impl MainMenuUi { let bg_img_id = ui.add_graphic(Graphic::Image(load_expect( bg_imgs.choose(&mut rng).unwrap(), ))); - // Load fonts - let fonts = Fonts::load(&mut ui).expect("Failed to load fonts"); + // Load language + let voxygen_i18n = load_expect::(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); + // Load fonts. + let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) + .expect("Impossible to load fonts!"); Self { ui, ids, imgs, rot_imgs, - fonts, username: networking.username.clone(), password: "".to_owned(), server_address: networking.servers[networking.default_server].clone(), @@ -203,6 +205,8 @@ impl MainMenuUi { time: 0.0, show_disclaimer: global_state.settings.show_disclaimer, bg_img_id, + voxygen_i18n, + fonts, } } @@ -220,10 +224,7 @@ impl MainMenuUi { const TEXT_COLOR_2: Color = Color::Rgba(1.0, 1.0, 1.0, 0.2); //const INACTIVE: Color = Color::Rgba(0.47, 0.47, 0.47, 0.47); - let localized_strings = load_expect::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )); - let intro_text = &localized_strings.get("main.login_process"); + let intro_text = &self.voxygen_i18n.get("main.login_process"); // Tooltip let _tooltip = Tooltip::new({ @@ -238,8 +239,9 @@ impl MainMenuUi { 5.0, ) }) - .title_font_size(15) - .desc_font_size(10) + .title_font_size(self.fonts.cyri.scale(15)) + .desc_font_size(self.fonts.cyri.scale(10)) + .font_id(self.fonts.cyri.conrod_id) .title_text_color(TEXT_COLOR) .desc_text_color(TEXT_COLOR_2); @@ -256,14 +258,14 @@ impl MainMenuUi { Text::new(&version) .color(TEXT_COLOR) .top_right_with_margins_on(ui_widgets.window, 5.0, 5.0) - .font_id(self.fonts.cyri) - .font_size(14) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) .set(self.ids.version, ui_widgets); // Popup (Error/Info) if let Some(popup_data) = &self.popup { let text = Text::new(&popup_data.msg) .rgba(1.0, 1.0, 1.0, if self.connect { fade_msg } else { 1.0 }) - .font_id(self.fonts.cyri); + .font_id(self.fonts.cyri.conrod_id); Rectangle::fill_with([65.0 * 6.0, 140.0], color::TRANSPARENT) .rgba(0.1, 0.1, 0.1, if self.connect { 0.0 } else { 1.0 }) .parent(ui_widgets.window) @@ -281,14 +283,14 @@ impl MainMenuUi { .set(self.ids.error_frame, ui_widgets); if self.connect { text.mid_top_with_margin_on(self.ids.error_frame, 10.0) - .font_id(self.fonts.alkhemi) + .font_id(self.fonts.alkhemi.conrod_id) .bottom_left_with_margins_on(ui_widgets.window, 60.0, 60.0) - .font_size(70) + .font_size(self.fonts.cyri.scale(70)) .set(self.ids.login_error, ui_widgets); } else { text.mid_top_with_margin_on(self.ids.error_frame, 10.0) - .font_id(self.fonts.cyri) - .font_size(25) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(25)) .set(self.ids.login_error, ui_widgets); }; if Button::image(self.imgs.button) @@ -305,8 +307,8 @@ impl MainMenuUi { .press_image(self.imgs.button_press) .label_y(Relative::Scalar(2.0)) .label(&popup_data.button_text) - .label_font_id(self.fonts.cyri) - .label_font_size(15) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(15)) .label_color(TEXT_COLOR) .set(self.ids.button_ok, ui_widgets) .was_clicked() @@ -328,14 +330,14 @@ impl MainMenuUi { .set(self.ids.banner, ui_widgets); Image::new(self.imgs.banner_top) - .w_h(65.0 * 6.0, 1.0 * 6.0) - .mid_top_with_margin_on(self.ids.banner, 0.0) + .w_h(70.0 * 6.0, 34.0) + .mid_top_with_margin_on(self.ids.banner, -34.0) .set(self.ids.banner_top, ui_widgets); // Logo Image::new(self.imgs.v_logo) .w_h(123.0 * 2.5, 35.0 * 2.5) - .mid_top_with_margin_on(self.ids.banner_top, 40.0) + .mid_top_with_margin_on(self.ids.banner_top, 45.0) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.95))) .set(self.ids.v_logo, ui_widgets); @@ -347,16 +349,16 @@ impl MainMenuUi { .scroll_kids_vertically() .set(self.ids.disc_window, ui_widgets); - Text::new(&localized_strings.get("common.disclaimer")) + Text::new(&self.voxygen_i18n.get("common.disclaimer")) .top_left_with_margins_on(self.ids.disc_window, 30.0, 40.0) - .font_size(35) - .font_id(self.fonts.alkhemi) + .font_size(self.fonts.cyri.scale(35)) + .font_id(self.fonts.alkhemi.conrod_id) .color(TEXT_COLOR) .set(self.ids.disc_text_1, ui_widgets); - Text::new(&localized_strings.get("main.notice")) + Text::new(&self.voxygen_i18n.get("main.notice")) .top_left_with_margins_on(self.ids.disc_window, 110.0, 40.0) - .font_size(26) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(26)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.disc_text_2, ui_widgets); if Button::image(self.imgs.button) @@ -366,9 +368,9 @@ impl MainMenuUi { .press_image(self.imgs.button_press) .label_y(Relative::Scalar(2.0)) .label("Accept") - .label_font_size(22) + .label_font_size(self.fonts.cyri.scale(22)) .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri) + .label_font_id(self.fonts.cyri.conrod_id) .set(self.ids.disc_button, ui_widgets) .was_clicked() { @@ -384,8 +386,8 @@ impl MainMenuUi { self.connect = true; self.connecting = Some(std::time::Instant::now()); self.popup = Some(PopupData { - msg: [localized_strings.get("main.connecting"), "..."].concat(), - button_text: localized_strings.get("common.cancel").to_owned(), + msg: [self.voxygen_i18n.get("main.connecting"), "..."].concat(), + button_text: self.voxygen_i18n.get("common.cancel").to_owned(), popup_type: PopupType::ConnectionInfo, }); @@ -408,8 +410,8 @@ impl MainMenuUi { .set(self.ids.info_bottom, ui_widgets); Text::new(intro_text) .top_left_with_margins_on(self.ids.info_frame, 15.0, 15.0) - .font_size(20) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(20)) + .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.info_text, ui_widgets); @@ -422,8 +424,8 @@ impl MainMenuUi { self.connect = true; self.connecting = Some(std::time::Instant::now()); self.popup = Some(PopupData { - msg: [localized_strings.get("main.creating_world"), "..."].concat(), - button_text: localized_strings.get("common.cancel").to_owned(), + msg: [self.voxygen_i18n.get("main.creating_world"), "..."].concat(), + button_text: self.voxygen_i18n.get("common.cancel").to_owned(), popup_type: PopupType::ConnectionInfo, }); }; @@ -433,15 +435,15 @@ impl MainMenuUi { Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.97)) .mid_top_with_margin_on(self.ids.banner_top, 160.0) .set(self.ids.usrnm_bg, ui_widgets); - Image::new(self.imgs.input_bg) + Image::new(self.imgs.input_bg_top) .w_h(337.0, 67.0) .middle_of(self.ids.usrnm_bg) .set(self.ids.username_bg, ui_widgets); for event in TextBox::new(&self.username) .w_h(290.0, 30.0) - .mid_bottom_with_margin_on(self.ids.username_bg, 44.0 / 2.0) - .font_size(22) - .font_id(self.fonts.cyri) + .mid_bottom_with_margin_on(self.ids.username_bg, 38.0 / 2.0) + .font_size(self.fonts.cyri.scale(22)) + .font_id(self.fonts.cyri.conrod_id) .text_color(TEXT_COLOR) // transparent background .color(TRANSPARENT) @@ -463,7 +465,7 @@ impl MainMenuUi { /*Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.97)) .down_from(self.ids.usrnm_bg, 30.0) .set(self.ids.passwd_bg, ui_widgets); - Image::new(self.imgs.input_bg) + Image::new(self.imgs.input_bg_mid) .w_h(337.0, 67.0) .middle_of(self.ids.passwd_bg) .color(Some(INACTIVE)) @@ -471,8 +473,8 @@ impl MainMenuUi { for event in TextBox::new(&self.password) .w_h(290.0, 30.0) .mid_bottom_with_margin_on(self.ids.password_bg, 44.0 / 2.0) - .font_size(22) - .font_id(self.fonts.cyri) + .font_size(self.fonts.cyri.scale(22)) + .font_id(self.fonts.cyri.conrod_id) .text_color(TEXT_COLOR) // transparent background .color(TRANSPARENT) @@ -524,8 +526,8 @@ impl MainMenuUi { //.press_image(self.imgs.button_press) .label_y(Relative::Scalar(2.0)) .label(&text) - .label_font_size(20) - .label_font_id(self.fonts.cyri) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR), ui_widgets, ) @@ -542,9 +544,9 @@ impl MainMenuUi { .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .label_y(Relative::Scalar(2.0)) - .label(&localized_strings.get("common.close")) - .label_font_size(20) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.close")) + .label_font_size(self.fonts.cyri.scale(20)) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) .set(self.ids.servers_close, ui_widgets) .was_clicked() @@ -556,15 +558,15 @@ impl MainMenuUi { Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.97)) .down_from(self.ids.usrnm_bg, 30.0) .set(self.ids.srvr_bg, ui_widgets); - Image::new(self.imgs.input_bg) + Image::new(self.imgs.input_bg_bot) .w_h(337.0, 67.0) .middle_of(self.ids.srvr_bg) .set(self.ids.address_bg, ui_widgets); for event in TextBox::new(&self.server_address) .w_h(290.0, 30.0) - .mid_bottom_with_margin_on(self.ids.address_bg, 44.0 / 2.0) - .font_size(22) - .font_id(self.fonts.cyri) + .mid_top_with_margin_on(self.ids.address_bg, 28.0 / 2.0) + .font_size(self.fonts.cyri.scale(22)) + .font_id(self.fonts.cyri.conrod_id) .text_color(TEXT_COLOR) // transparent background .color(TRANSPARENT) @@ -587,10 +589,10 @@ impl MainMenuUi { .w_h(258.0, 55.0) .down_from(self.ids.address_bg, 20.0) .align_middle_x_of(self.ids.address_bg) - .label(&localized_strings.get("common.multiplayer")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.multiplayer")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) - .label_font_size(22) + .label_font_size(self.fonts.cyri.scale(22)) .label_y(Relative::Scalar(5.0)) /*.with_tooltip( tooltip_manager, @@ -614,10 +616,10 @@ impl MainMenuUi { .w_h(258.0, 55.0) .down_from(self.ids.login_button, 20.0) .align_middle_x_of(self.ids.address_bg) - .label(&localized_strings.get("common.singleplayer")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.singleplayer")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) - .label_font_size(22) + .label_font_size(self.fonts.cyri.scale(22)) .label_y(Relative::Scalar(5.0)) .label_x(Relative::Scalar(2.0)) .set(self.ids.singleplayer_button, ui_widgets) @@ -632,10 +634,10 @@ impl MainMenuUi { .bottom_left_with_margins_on(ui_widgets.window, 60.0, 30.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("common.quit")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.quit")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) .label_y(Relative::Scalar(3.0)) .set(self.ids.quit_button, ui_widgets) .was_clicked() @@ -649,10 +651,10 @@ impl MainMenuUi { .up_from(self.ids.quit_button, 8.0) //.hover_image(self.imgs.button_hover) //.press_image(self.imgs.button_press) - .label(&localized_strings.get("common.settings")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.settings")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR_2) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) .label_y(Relative::Scalar(3.0)) .set(self.ids.settings_button, ui_widgets) .was_clicked() @@ -666,10 +668,10 @@ impl MainMenuUi { .up_from(self.ids.settings_button, 8.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&localized_strings.get("common.servers")) - .label_font_id(self.fonts.cyri) + .label(&self.voxygen_i18n.get("common.servers")) + .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) - .label_font_size(20) + .label_font_size(self.fonts.cyri.scale(20)) .label_y(Relative::Scalar(3.0)) .set(self.ids.servers_button, ui_widgets) .was_clicked() diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index c797fcd96e..4e6fd5b282 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -26,6 +26,7 @@ gfx_defines! { light_shadow_count: [u32; 4] = "light_shadow_count", medium: [u32; 4] = "medium", select_pos: [i32; 4] = "select_pos", + gamma: [f32; 4] = "gamma", } constant Light { @@ -53,6 +54,7 @@ impl Globals { shadow_count: usize, medium: BlockKind, select_pos: Option>, + gamma: f32, ) -> Self { Self { view_mat: arr_to_mat(view_mat.into_col_array()), @@ -70,6 +72,7 @@ impl Globals { .map(|sp| Vec4::from(sp) + Vec4::unit_w()) .unwrap_or(Vec4::zero()) .into_array(), + gamma: [gamma; 4], } } } @@ -89,6 +92,7 @@ impl Default for Globals { 0, BlockKind::Air, None, + 1.0, ) } } diff --git a/voxygen/src/render/pipelines/ui.rs b/voxygen/src/render/pipelines/ui.rs index 6f3ab102ea..35ba91bb86 100644 --- a/voxygen/src/render/pipelines/ui.rs +++ b/voxygen/src/render/pipelines/ui.rs @@ -10,6 +10,7 @@ gfx_defines! { pos: [f32; 2] = "v_pos", uv: [f32; 2] = "v_uv", color: [f32; 4] = "v_color", + center: [f32; 2] = "v_center", mode: u32 = "v_mode", } @@ -55,11 +56,23 @@ pub const MODE_TEXT: u32 = 0; pub const MODE_IMAGE: u32 = 1; /// Ignore `tex` and draw simple, colored 2D geometry. pub const MODE_GEOMETRY: u32 = 2; +/// Draw an image from the texture at `tex` in the fragment shader, with the +/// source rectangle rotated to face north. +/// +/// FIXME: Make more principled. +pub const MODE_IMAGE_SOURCE_NORTH: u32 = 3; +/// Draw an image from the texture at `tex` in the fragment shader, with the +/// target rectangle rotated to face north. +/// +/// FIXME: Make more principled. +pub const MODE_IMAGE_TARGET_NORTH: u32 = 5; pub enum Mode { Text, Image, Geometry, + ImageSourceNorth, + ImageTargetNorth, } impl Mode { @@ -68,6 +81,8 @@ impl Mode { Mode::Text => MODE_TEXT, Mode::Image => MODE_IMAGE, Mode::Geometry => MODE_GEOMETRY, + Mode::ImageSourceNorth => MODE_IMAGE_SOURCE_NORTH, + Mode::ImageTargetNorth => MODE_IMAGE_TARGET_NORTH, } } } @@ -78,10 +93,16 @@ pub fn create_quad( color: Rgba, mode: Mode, ) -> Quad { + let center = if let Mode::ImageSourceNorth = mode { + uv_rect.center().into_array() + } else { + rect.center().into_array() + }; let mode_val = mode.value(); let v = |pos, uv| Vertex { pos, uv, + center, color: color.into_array(), mode: mode_val, }; @@ -118,10 +139,12 @@ pub fn create_tri( color: Rgba, mode: Mode, ) -> Tri { + let center = [0.0, 0.0]; let mode_val = mode.value(); let v = |pos, uv| Vertex { pos, uv, + center, color: color.into_array(), mode: mode_val, }; diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index d35a4c240b..a0d0d59e54 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -190,7 +190,7 @@ impl Camera { } /// Get the focus position of the camera. - pub fn get_focus_pos(&self) -> Vec3 { self.tgt_focus } + pub fn get_focus_pos(&self) -> Vec3 { self.focus } /// Set the focus position of the camera. pub fn set_focus_pos(&mut self, focus: Vec3) { self.tgt_focus = focus; } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 23d676d882..160384e1bc 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -149,6 +149,7 @@ impl Scene { renderer: &mut Renderer, audio: &mut AudioFrontend, client: &Client, + gamma: f32, ) { // Get player position. let player_pos = client @@ -296,6 +297,7 @@ impl Scene { .map(|b| b.kind()) .unwrap_or(BlockKind::Air), self.select_pos, + gamma, )]) .expect("Failed to update global constants"); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 508b8cbe6a..1a2be92867 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -126,9 +126,6 @@ impl PlayState for SessionState { let mut clock = Clock::start(); self.client.borrow_mut().clear_terrain(); - // Kill the title music if it is still playing - global_state.audio.stop_title_music(); - // Send startup commands to the server if global_state.settings.send_logon_commands { for cmd in &global_state.settings.logon_commands { @@ -173,7 +170,16 @@ impl PlayState for SessionState { (None, None) } }; - self.scene.set_select_pos(select_pos); + // Only highlight collectables + self.scene.set_select_pos(select_pos.filter(|sp| { + self.client + .borrow() + .state() + .terrain() + .get(*sp) + .map(|b| b.is_collectible()) + .unwrap_or(false) + })); // Handle window events. for event in global_state.window.fetch_events(&mut global_state.settings) { @@ -534,6 +540,10 @@ impl PlayState for SessionState { global_state.settings.save_to_file_warn(); self.scene.camera_mut().set_fov_deg(new_fov); }, + HudEvent::ChangeGamma(new_gamma) => { + global_state.settings.graphics.gamma = new_gamma; + global_state.settings.save_to_file_warn(); + }, HudEvent::ChangeAaMode(new_aa_mode) => { // Do this first so if it crashes the setting isn't saved :) global_state @@ -573,6 +583,7 @@ impl PlayState for SessionState { ) .unwrap(); localized_strings.log_missing_entries(); + self.hud.update_language(localized_strings.clone()); }, HudEvent::ToggleFullscreen => { global_state @@ -592,6 +603,7 @@ impl PlayState for SessionState { global_state.window.renderer_mut(), &mut global_state.audio, &self.client.borrow(), + global_state.settings.graphics.gamma, ); // Render the session. diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 87822b87b0..8d339f2837 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -191,6 +191,7 @@ pub struct GraphicsSettings { pub view_distance: u32, pub max_fps: u32, pub fov: u16, + pub gamma: f32, pub aa_mode: AaMode, pub cloud_mode: CloudMode, pub fluid_mode: FluidMode, @@ -204,6 +205,7 @@ impl Default for GraphicsSettings { view_distance: 10, max_fps: 60, fov: 50, + gamma: 1.0, aa_mode: AaMode::Fxaa, cloud_mode: CloudMode::Regular, fluid_mode: FluidMode::Shiny, @@ -221,6 +223,7 @@ pub struct AudioSettings { pub master_volume: f32, pub music_volume: f32, pub sfx_volume: f32, + pub max_sfx_channels: usize, /// Audio Device that Voxygen will use to play audio. pub audio_device: Option, @@ -233,6 +236,7 @@ impl Default for AudioSettings { master_volume: 1.0, music_volume: 0.4, sfx_volume: 0.6, + max_sfx_channels: 10, audio_device: None, audio_on: true, } diff --git a/voxygen/src/ui/font_ids.rs b/voxygen/src/ui/font_ids.rs deleted file mode 100644 index d6c8a92643..0000000000 --- a/voxygen/src/ui/font_ids.rs +++ /dev/null @@ -1,30 +0,0 @@ -/// This macro will automatically load all specified assets, get the -/// corresponding FontIds and create a struct with all of them. -/// -/// Example usage: -/// ``` -/// font_ids! { -/// pub struct Fonts { -/// font1: "filename1", -/// font2: "filename2", -/// } -/// } -/// ``` -#[macro_export] -macro_rules! font_ids { - ($($v:vis struct $Ids:ident { $( $name:ident: $specifier:expr $(,)? )* })*) => { - $( - $v struct $Ids { - $( $v $name: conrod_core::text::font::Id, )* - } - - impl $Ids { - pub fn load(ui: &mut crate::ui::Ui) -> Result { - Ok(Self { - $( $name: ui.new_font(common::assets::load($specifier)?), )* - }) - } - } - )* - }; -} diff --git a/voxygen/src/ui/fonts.rs b/voxygen/src/ui/fonts.rs new file mode 100644 index 0000000000..c42b9abf3a --- /dev/null +++ b/voxygen/src/ui/fonts.rs @@ -0,0 +1,40 @@ +use crate::i18n::{Font, VoxygenFonts}; + +pub struct ConrodVoxygenFont { + metadata: Font, + pub conrod_id: conrod_core::text::font::Id, +} + +impl ConrodVoxygenFont { + pub fn new(font: &Font, ui: &mut crate::ui::Ui) -> ConrodVoxygenFont { + return Self { + metadata: font.clone(), + conrod_id: ui.new_font(common::assets::load_expect(&font.asset_key)), + }; + } + + /// Scale input size to final UI size + pub fn scale(&self, value: u32) -> u32 { self.metadata.scale(value) } +} + +macro_rules! conrod_fonts { + ($([ $( $name:ident$(,)? )* ])*) => { + $( + pub struct ConrodVoxygenFonts { + $(pub $name: ConrodVoxygenFont,)* + } + + impl ConrodVoxygenFonts { + pub fn load(voxygen_fonts: &VoxygenFonts, ui: &mut crate::ui::Ui) -> Result { + Ok(Self { + $( $name: ConrodVoxygenFont::new(voxygen_fonts.get(stringify!($name)).unwrap(), ui),)* + }) + } + } + )* + }; +} + +conrod_fonts! { + [opensans, metamorph, alkhemi, cyri, wizard] +} diff --git a/voxygen/src/ui/graphic/mod.rs b/voxygen/src/ui/graphic/mod.rs index 3fc704b30a..f1431aff08 100644 --- a/voxygen/src/ui/graphic/mod.rs +++ b/voxygen/src/ui/graphic/mod.rs @@ -6,7 +6,7 @@ pub use renderer::{SampleStrat, Transform}; use crate::render::{Renderer, Texture}; use dot_vox::DotVoxData; use guillotiere::{size2, SimpleAtlasAllocator}; -use hashbrown::HashMap; +use hashbrown::{hash_map::Entry, HashMap}; use image::{DynamicImage, RgbaImage}; use log::warn; use pixel_art::resize_pixel_art; @@ -26,6 +26,14 @@ pub enum Rotation { Cw90, Cw180, Cw270, + /// Orientation of source rectangle that always faces true north. + /// Simple hack to get around Conrod not having space for proper + /// rotation data (though it should be possible to add in other ways). + SourceNorth, + /// Orientation of target rectangle that always faces true north. + /// Simple hack to get around Conrod not having space for proper + /// rotation data (though it should be possible to add in other ways). + TargetNorth, } /// Images larger than this are stored in individual textures @@ -41,7 +49,7 @@ pub struct Id(u32); #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct TexId(usize); -type Parameters = (Id, Vec2, Aabr); +type Parameters = (Id, Vec2); type GraphicMap = HashMap; enum CacheLoc { @@ -130,6 +138,8 @@ impl GraphicCache { self.textures = vec![texture]; } + /// Source rectangle should be from 0 to 1, and represents a bounding box + /// for the source image of the graphic. pub fn cache_res( &mut self, renderer: &mut Renderer, @@ -137,15 +147,18 @@ impl GraphicCache { dims: Vec2, source: Aabr, rotation: Rotation, - ) -> Option<(Aabr, TexId)> { + ) -> Option<(Aabr, TexId)> { let dims = match rotation { Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x), Rotation::None | Rotation::Cw180 => dims, + Rotation::SourceNorth => dims, + Rotation::TargetNorth => dims, }; - let key = (graphic_id, dims, source.map(|e| e.to_bits())); // TODO: Replace this with rounded representation of source + let key = (graphic_id, dims); + // Rotate aabr according to requested rotation. let rotated_aabr = |Aabr { min, max }| match rotation { - Rotation::None => Aabr { min, max }, + Rotation::None | Rotation::SourceNorth | Rotation::TargetNorth => Aabr { min, max }, Rotation::Cw90 => Aabr { min: Vec2::new(min.x, max.y), max: Vec2::new(max.x, min.y), @@ -156,101 +169,115 @@ impl GraphicCache { max: Vec2::new(min.x, max.y), }, }; + // Scale aabr according to provided source rectangle. + let scaled_aabr = |aabr: Aabr<_>| { + let size: Vec2<_> = aabr.size().into(); + Aabr { + min: size.mul_add(source.min, aabr.min), + max: size.mul_add(source.max, aabr.min), + } + }; + // Apply all transformations. + // TODO: Verify rotation is being applied correctly. + let transformed_aabr = |aabr| rotated_aabr(scaled_aabr(aabr)); - if let Some(details) = self.cache_map.get(&key) { - let (idx, aabr) = match details.location { - CacheLoc::Atlas { - atlas_idx, aabr, .. - } => (self.atlases[atlas_idx].1, aabr), - CacheLoc::Texture { index } => { - (index, Aabr { - min: Vec2::new(0, 0), - // Note texture should always match the cached dimensions - max: dims, - }) - }, - }; + let details = match self.cache_map.entry(key) { + Entry::Occupied(details) => { + let details = details.get(); + let (idx, aabr) = match details.location { + CacheLoc::Atlas { + atlas_idx, aabr, .. + } => (self.atlases[atlas_idx].1, aabr), + CacheLoc::Texture { index } => { + (index, Aabr { + min: Vec2::new(0, 0), + // Note texture should always match the cached dimensions + max: dims, + }) + }, + }; - // Check if the cached version has been invalidated by replacing the underlying - // graphic - if !details.valid { - // Create image - let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; - // Transfer to the gpu - upload_image(renderer, aabr, &self.textures[idx], &image); + // Check if the cached version has been invalidated by replacing the underlying + // graphic + if !details.valid { + // Create image + let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; + // Transfer to the gpu + upload_image(renderer, aabr, &self.textures[idx], &image); + } + + return Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx))); + }, + Entry::Vacant(details) => details, + }; + + // Create image + let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; + + // Allocate space on the gpu + // Check size of graphic + // Graphics over a particular size are sent to their own textures + let location = if Vec2::::from(self.atlases[0].0.size().to_tuple()) + .map(|e| e as u16) + .map2(dims, |a, d| a as f32 * ATLAS_CUTTOFF_FRAC >= d as f32) + .reduce_and() + { + // Fit into an atlas + let mut loc = None; + for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() { + if let Some(rectangle) = atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y))) + { + let aabr = aabr_from_alloc_rect(rectangle); + loc = Some(CacheLoc::Atlas { atlas_idx, aabr }); + break; + } } - Some((rotated_aabr(aabr), TexId(idx))) - } else { - // Create image - let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; - - // Allocate space on the gpu - // Check size of graphic - // Graphics over a particular size are sent to their own textures - let location = if Vec2::::from(self.atlases[0].0.size().to_tuple()) - .map(|e| e as u16) - .map2(dims, |a, d| a as f32 * ATLAS_CUTTOFF_FRAC >= d as f32) - .reduce_and() - { - // Fit into an atlas - let mut loc = None; - for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() { - if let Some(rectangle) = - atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y))) - { - let aabr = aabr_from_alloc_rect(rectangle); - loc = Some(CacheLoc::Atlas { atlas_idx, aabr }); - break; - } - } - - match loc { - Some(loc) => loc, - // Create a new atlas - None => { - let (mut atlas, texture) = create_atlas_texture(renderer); - let aabr = atlas - .allocate(size2(i32::from(dims.x), i32::from(dims.y))) - .map(aabr_from_alloc_rect) - .unwrap(); - let tex_idx = self.textures.len(); - let atlas_idx = self.atlases.len(); - self.textures.push(texture); - self.atlases.push((atlas, tex_idx)); - CacheLoc::Atlas { atlas_idx, aabr } - }, - } - } else { - // Create a texture just for this - let texture = renderer.create_dynamic_texture(dims).unwrap(); - let index = self.textures.len(); - self.textures.push(texture); - CacheLoc::Texture { index } - }; - - let (idx, aabr) = match location { - CacheLoc::Atlas { - atlas_idx, aabr, .. - } => (self.atlases[atlas_idx].1, aabr), - CacheLoc::Texture { index } => { - (index, Aabr { - min: Vec2::new(0, 0), - // Note texture should always match the cached dimensions - max: dims, - }) + match loc { + Some(loc) => loc, + // Create a new atlas + None => { + let (mut atlas, texture) = create_atlas_texture(renderer); + let aabr = atlas + .allocate(size2(i32::from(dims.x), i32::from(dims.y))) + .map(aabr_from_alloc_rect) + .unwrap(); + let tex_idx = self.textures.len(); + let atlas_idx = self.atlases.len(); + self.textures.push(texture); + self.atlases.push((atlas, tex_idx)); + CacheLoc::Atlas { atlas_idx, aabr } }, - }; - // Upload - upload_image(renderer, aabr, &self.textures[idx], &image); - // Insert into cached map - self.cache_map.insert(key, CachedDetails { - location, - valid: true, - }); + } + } else { + // Create a texture just for this + let texture = renderer.create_dynamic_texture(dims).unwrap(); + let index = self.textures.len(); + self.textures.push(texture); + CacheLoc::Texture { index } + }; - Some((rotated_aabr(aabr), TexId(idx))) - } + let (idx, aabr) = match location { + CacheLoc::Atlas { + atlas_idx, aabr, .. + } => (self.atlases[atlas_idx].1, aabr), + CacheLoc::Texture { index } => { + (index, Aabr { + min: Vec2::new(0, 0), + // Note texture should always match the cached dimensions + max: dims, + }) + }, + }; + // Upload + upload_image(renderer, aabr, &self.textures[idx], &image); + // Insert into cached map + details.insert(CachedDetails { + location, + valid: true, + }); + + Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx))) } } diff --git a/voxygen/src/ui/img_ids.rs b/voxygen/src/ui/img_ids.rs index bf18a99ed8..f3cfc0f65f 100644 --- a/voxygen/src/ui/img_ids.rs +++ b/voxygen/src/ui/img_ids.rs @@ -106,6 +106,8 @@ pub struct Rotations { pub cw90: conrod_core::image::Id, pub cw180: conrod_core::image::Id, pub cw270: conrod_core::image::Id, + pub source_north: conrod_core::image::Id, + pub target_north: conrod_core::image::Id, } /// This macro will automatically load all specified assets, get the diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 598040080d..1016c2a134 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -6,7 +6,7 @@ mod widgets; #[macro_use] pub mod img_ids; #[macro_use] -mod font_ids; +pub mod fonts; pub use event::Event; pub use graphic::{Graphic, SampleStrat, Transform}; @@ -28,6 +28,7 @@ use crate::{ window::Window, Error, }; +use ::image::GenericImageView; use cache::Cache; use common::{assets, util::srgba_to_linear}; use conrod_core::{ @@ -43,6 +44,7 @@ use conrod_core::{ use graphic::{Rotation, TexId}; use log::{error, warn}; use std::{ + f32, f64, fs::File, io::{BufReader, Read}, ops::Range, @@ -94,7 +96,7 @@ impl assets::Asset for Font { } pub struct Ui { - ui: conrod_core::Ui, + pub ui: conrod_core::Ui, image_map: Map<(graphic::Id, Rotation)>, cache: Cache, // Draw commands for the next render @@ -172,6 +174,16 @@ impl Ui { cw90: self.image_map.insert((graphic_id, Rotation::Cw90)), cw180: self.image_map.insert((graphic_id, Rotation::Cw180)), cw270: self.image_map.insert((graphic_id, Rotation::Cw270)), + // Hacky way to make sure a source rectangle always faces north regardless of player + // orientation. + // This is an easy way to get around Conrod's lack of rotation data for images (for this + // specific use case). + source_north: self.image_map.insert((graphic_id, Rotation::SourceNorth)), + // Hacky way to make sure a target rectangle always faces north regardless of player + // orientation. + // This is an easy way to get around Conrod's lack of rotation data for images (for this + // specific use case). + target_north: self.image_map.insert((graphic_id, Rotation::TargetNorth)), } } @@ -420,46 +432,88 @@ impl Ui { PrimitiveKind::Image { image_id, color, - source_rect: _, // TODO: <-- use this + source_rect, } => { let (graphic_id, rotation) = self .image_map .get(&image_id) .expect("Image does not exist in image map"); let graphic_cache = self.cache.graphic_cache_mut(); - - match graphic_cache.get_graphic(*graphic_id) { - Some(Graphic::Blank) | None => continue, - _ => {}, - } + let gl_aabr = gl_aabr(rect); + let (source_aabr, gl_size) = { + // Transform the source rectangle into uv coordinate. + // TODO: Make sure this is right. Especially the conversions. + let ((uv_l, uv_r, uv_b, uv_t), gl_size) = + match graphic_cache.get_graphic(*graphic_id) { + Some(Graphic::Blank) | None => continue, + Some(Graphic::Image(image)) => { + source_rect.and_then(|src_rect| { + let (image_w, image_h) = image.dimensions(); + let (source_w, source_h) = src_rect.w_h(); + let gl_size = gl_aabr.size(); + if image_w == 0 + || image_h == 0 + || source_w < 1.0 + || source_h < 1.0 + || gl_size.reduce_partial_max() < f32::EPSILON + { + None + } else { + // Multiply drawn image size by ratio of original image + // size to + // source rectangle size (since as the proportion of the + // image gets + // smaller, the drawn size should get bigger), up to the + // actual + // size of the original image. + let ratio_x = (image_w as f64 / source_w).min( + (image_w as f64 / (gl_size.w * half_res.x) as f64) + .max(1.0), + ); + let ratio_y = (image_h as f64 / source_h).min( + (image_h as f64 / (gl_size.h * half_res.y) as f64) + .max(1.0), + ); + let (l, r, b, t) = src_rect.l_r_b_t(); + Some(( + ( + l / image_w as f64, /* * ratio_x*/ + r / image_w as f64, /* * ratio_x*/ + b / image_h as f64, /* * ratio_y*/ + t / image_h as f64, /* * ratio_y*/ + ), + Extent2::new( + (gl_size.w as f64 * ratio_x) as f32, + (gl_size.h as f64 * ratio_y) as f32, + ), + )) + /* ((l / image_w as f64), + (r / image_w as f64), + (b / image_h as f64), + (t / image_h as f64)) */ + } + }) + }, + // No easy way to interpret source_rect for voxels... + Some(Graphic::Voxel(..)) => None, + } + .unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size())); + ( + Aabr { + min: Vec2::new(uv_l, uv_b), + max: Vec2::new(uv_r, uv_t), + }, + gl_size, + ) + }; let color = srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into()); - let gl_aabr = gl_aabr(rect); let resolution = Vec2::new( - (gl_aabr.size().w * half_res.x).round() as u16, - (gl_aabr.size().h * half_res.y).round() as u16, + (gl_size.w * half_res.x).round() as u16, + (gl_size.h * half_res.y).round() as u16, ); - // Transform the source rectangle into uv coordinate. - // TODO: Make sure this is right. - let source_aabr = { - let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); - /*match source_rect { - Some(src_rect) => { - let (l, r, b, t) = src_rect.l_r_b_t(); - ((l / image_w) as f32, - (r / image_w) as f32, - (b / image_h) as f32, - (t / image_h) as f32) - } - None => (0.0, 1.0, 0.0, 1.0), - };*/ - Aabr { - min: Vec2::new(uv_l, uv_b), - max: Vec2::new(uv_r, uv_t), - } - }; // Cache graphic at particular resolution. let (uv_aabr, tex_id) = match graphic_cache.cache_res( @@ -500,7 +554,13 @@ impl Ui { State::Image(_) => {}, } - mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image)); + mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation { + Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => { + UiMode::Image + }, + Rotation::SourceNorth => UiMode::ImageSourceNorth, + Rotation::TargetNorth => UiMode::ImageTargetNorth, + })); }, PrimitiveKind::Text { color, diff --git a/voxygen/tests/check_i18n_files.rs b/voxygen/tests/check_i18n_files.rs new file mode 100644 index 0000000000..2efe365826 --- /dev/null +++ b/voxygen/tests/check_i18n_files.rs @@ -0,0 +1,250 @@ +use git2::Repository; +use ron::de::from_bytes; +use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, +}; +use veloren_voxygen::i18n::VoxygenLocalization; + +/// List localization files as a PathBuf vector +fn i18n_files(i18n_dir: &Path) -> Vec { + fs::read_dir(i18n_dir) + .unwrap() + .map(|res| res.map(|e| e.path()).unwrap()) + .filter(|e| match e.extension() { + Some(ext) => ext == "ron", + None => false, + }) + .collect() +} + +#[derive(Debug, PartialEq)] +enum LocalizationState { + UpToDate, + NotFound, + Outdated, + Unknown, + Unused, +} + +#[derive(Debug)] +struct LocalizationEntryState { + pub key_line: Option, + pub chuck_line_range: Option<(usize, usize)>, + pub commit_id: Option, + pub state: LocalizationState, +} + +impl LocalizationEntryState { + pub fn new() -> LocalizationEntryState { + LocalizationEntryState { + key_line: None, + chuck_line_range: None, + commit_id: None, + state: LocalizationState::Unknown, + } + } +} + +/// Returns the Git blob associated with the given reference and path +fn read_file_from_path<'a>( + repo: &'a git2::Repository, + reference: &git2::Reference, + path: &std::path::Path, +) -> git2::Blob<'a> { + let tree = reference + .peel_to_tree() + .expect("Impossible to peel HEAD to a tree object"); + tree.get_path(path) + .expect(&format!( + "Impossible to find the file {:?} in reference {:?}", + path, + reference.name() + )) + .to_object(&repo) + .unwrap() + .peel_to_blob() + .expect("Impossible to fetch the Git object") +} + +fn generate_key_version<'a>( + repo: &'a git2::Repository, + localization: &VoxygenLocalization, + path: &std::path::Path, + file_blob: &git2::Blob, +) -> HashMap { + let mut keys: HashMap = localization + .string_map + .keys() + .map(|k| (k.to_owned(), LocalizationEntryState::new())) + .collect(); + let mut to_process: HashSet<&String> = localization.string_map.keys().map(|k| k).collect(); + let mut line_nb = 0; + + // Find key start lines + for line in std::str::from_utf8(file_blob.content()) + .expect("UTF-8 file") + .split('\n') + { + line_nb += 1; + + let mut found_key = None; + + for key in to_process.iter() { + if line.contains(key.as_str()) { + found_key = Some(key.to_owned()); + break; + } + } + + if let Some(key) = found_key { + keys.get_mut(key).unwrap().key_line = Some(line_nb); + to_process.remove(&key); + }; + } + + // Find commit for each keys + repo.blame_file(path, None) + .expect("Impossible to generate the Git blame") + .iter() + .for_each(|e: git2::BlameHunk| { + for state in keys.values_mut() { + let line = state.key_line.unwrap(); + + if line >= e.final_start_line() && line < e.final_start_line() + e.lines_in_hunk() { + state.chuck_line_range = Some(( + e.final_start_line(), + e.final_start_line() + e.lines_in_hunk(), + )); + state.commit_id = match state.commit_id { + Some(existing_commit) => { + match repo.graph_descendant_of(e.final_commit_id(), existing_commit) { + Ok(true) => Some(e.final_commit_id()), + Ok(false) => Some(existing_commit), + Err(err) => panic!(err), + } + }, + None => Some(e.final_commit_id()), + }; + } + } + }); + + keys +} + +#[test] +#[ignore] +fn test_all_localizations<'a>() { + // Generate paths + let i18n_asset_path = Path::new("assets/voxygen/i18n/"); + let en_i18n_path = i18n_asset_path.join("en.ron"); + let root_dir = std::env::current_dir() + .map(|p| p.parent().expect("").to_owned()) + .unwrap(); + let i18n_path = root_dir.join(i18n_asset_path); + + if !root_dir.join(&en_i18n_path).is_file() { + panic!("Reference language file not found {:?}", &en_i18n_path) + } + + // Initialize Git objects + let repo = Repository::discover(&root_dir).expect(&format!( + "Failed to open the Git repository at {:?}", + &root_dir + )); + let head_ref = repo.head().expect("Impossible to get the HEAD reference"); + + // Read HEAD for the reference language file + let i18n_en_blob = read_file_from_path(&repo, &head_ref, &en_i18n_path); + let loc: VoxygenLocalization = + from_bytes(i18n_en_blob.content()).expect("Expect to parse the RON file"); + let i18n_references: HashMap = + generate_key_version(&repo, &loc, &en_i18n_path, &i18n_en_blob); + + // Compare to other reference files + let i18n_files = i18n_files(&i18n_path); + for file in i18n_files { + let relfile = file.strip_prefix(&root_dir).unwrap(); + let mut uptodate_entries = 0; + if relfile == en_i18n_path { + continue; + } + println!("{:?}", relfile); + + // Find the localization entry state + let current_blob = read_file_from_path(&repo, &head_ref, &relfile); + let current_loc: VoxygenLocalization = + from_bytes(current_blob.content()).expect("Expect to parse the RON file"); + let mut current_i18n = generate_key_version(&repo, ¤t_loc, &relfile, ¤t_blob); + for (ref_key, ref_state) in i18n_references.iter() { + match current_i18n.get_mut(ref_key) { + Some(state) => { + let commit_id = state.commit_id.unwrap(); + let ref_commit_id = ref_state.commit_id.unwrap(); + if commit_id != ref_commit_id + && !repo + .graph_descendant_of(commit_id, ref_commit_id) + .unwrap_or(false) + { + state.state = LocalizationState::Outdated; + } else { + state.state = LocalizationState::UpToDate; + } + }, + None => { + current_i18n.insert(ref_key.to_owned(), LocalizationEntryState { + key_line: None, + chuck_line_range: None, + commit_id: None, + state: LocalizationState::NotFound, + }); + }, + } + } + + let ref_keys: HashSet<&String> = i18n_references.keys().collect(); + for (_, state) in current_i18n + .iter_mut() + .filter(|&(k, _)| !ref_keys.contains(k)) + { + state.state = LocalizationState::Unused; + } + + // Display + println!( + "{:10} {:60}{:40} {:40}", + "State", + "Key name", + relfile.to_str().unwrap(), + en_i18n_path.to_str().unwrap() + ); + + let mut sorted_keys: Vec<&String> = current_i18n.keys().collect(); + sorted_keys.sort(); + for key in sorted_keys { + let state = current_i18n.get(key).unwrap(); + if state.state != LocalizationState::UpToDate { + println!( + "[{:9}] {:60}{:40} {:40}", + format!("{:?}", state.state), + key, + state + .commit_id + .map(|s| format!("{}", s)) + .unwrap_or("None".to_string()), + i18n_references + .get(key) + .map(|s| s.commit_id) + .flatten() + .map(|s| format!("{}", s)) + .unwrap_or("None".to_string()), + ); + } else { + uptodate_entries += 1; + } + } + println!("{} entries are up-to-date\n", uptodate_entries); + } +} diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 5737c8370c..0707abd0d6 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -239,6 +239,9 @@ impl MapConfig { } } + let water_color_factor = 2.0; + let g_water = 32.0 * water_color_factor; + let b_water = 64.0 * water_color_factor; let rgba = match (river_kind, (is_water, true_alt >= true_sea_level)) { (_, (false, _)) | (None, (_, true)) => { let (r, g, b) = ( @@ -251,7 +254,7 @@ impl MapConfig { 0.0 }) .sqrt(), - if is_shaded { 0.2 + (alt * 0.8) } else { alt }, + if is_shaded { 0.4 + (alt * 0.6) } else { alt }, (if is_shaded { alt } else { alt } * if is_humidity { humidity as f64 @@ -272,17 +275,22 @@ impl MapConfig { }, (Some(RiverKind::Ocean), _) => ( 0, - ((32.0 - water_depth * 32.0) * 1.0) as u8, - ((64.0 - water_depth * 64.0) * 1.0) as u8, + ((g_water - water_depth * g_water) * 1.0) as u8, + ((b_water - water_depth * b_water) * 1.0) as u8, + 255, + ), + (Some(RiverKind::River { .. }), _) => ( + 0, + g_water as u8 + (alt * (127.0 - g_water)) as u8, + b_water as u8 + (alt * (255.0 - b_water)) as u8, 255, ), - (Some(RiverKind::River { .. }), _) => { - (0, 32 + (alt * 95.0) as u8, 64 + (alt * 191.0) as u8, 255) - }, (None, _) | (Some(RiverKind::Lake { .. }), _) => ( 0, - (((32.0 + water_alt * 95.0) + (-water_depth * 32.0)) * 1.0) as u8, - (((64.0 + water_alt * 191.0) + (-water_depth * 64.0)) * 1.0) as u8, + (((g_water + water_alt * (127.0 - 32.0)) + (-water_depth * g_water)) * 1.0) + as u8, + (((b_water + water_alt * (255.0 - b_water)) + (-water_depth * b_water)) + * 1.0) as u8, 255, ), }; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 594e158696..feb3643eb5 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -292,6 +292,9 @@ impl WorldFile { pub struct WorldSim { pub seed: u32, + /// Maximum height above sea level of any chunk in the map (not including + /// post-erosion warping, cliffs, and other things like that). + pub max_height: f32, pub(crate) chunks: Vec, pub(crate) locations: Vec, @@ -571,7 +574,7 @@ impl WorldSim { fn spring(x: f64, pow: f64) -> f64 { x.abs().powf(pow) * x.signum() } - (0.0 + alt_main + 0.0 + alt_main + (gen_ctx .small_nz .get( @@ -587,7 +590,7 @@ impl WorldSim { .mul(0.3) .add(1.0) .mul(0.4) - + spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0).mul(0.045)) + + spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0).mul(0.045) }; // Now we can compute the final altitude using chaos. @@ -666,7 +669,7 @@ impl WorldSim { let alt_exp_max_uniform = inv_func(max_epsilon); let erosion_factor = |x: f64| { - ((inv_func(x) - alt_exp_min_uniform) / (alt_exp_max_uniform - alt_exp_min_uniform)) + (inv_func(x) - alt_exp_min_uniform) / (alt_exp_max_uniform - alt_exp_min_uniform) }; let rock_strength_div_factor = (2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0; let theta_func = |_posi| 0.4; @@ -1288,6 +1291,7 @@ impl WorldSim { let mut this = Self { seed, + max_height: maxh as f32, chunks, locations: Vec::new(), gen_ctx, @@ -1306,7 +1310,11 @@ impl WorldSim { pub fn get_map(&self) -> Vec { let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y]; // TODO: Parallelize again. - MapConfig::default().generate(&self, |pos, (r, g, b, a)| { + MapConfig { + gain: self.max_height, + ..MapConfig::default() + } + .generate(&self, |pos, (r, g, b, a)| { v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]); }); v diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index 5333d20657..fa138c40bf 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -175,12 +175,12 @@ pub fn uniform_noise( let mut noise = (0..WORLD_SIZE.x * WORLD_SIZE.y) .into_par_iter() .filter_map(|i| { - (f( + f( i, (uniform_idx_as_vec2(i) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) .map(|e| e as f64), ) - .map(|res| (i, res))) + .map(|res| (i, res)) }) .collect::>();