Merge remote-tracking branch 'origin/master' into clientstates

This commit is contained in:
timokoesters
2020-02-24 21:34:17 +01:00
133 changed files with 4209 additions and 2182 deletions

60
.github/workflows/artifacts.yml vendored Normal file
View File

@ -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/

View File

@ -109,8 +109,8 @@ coverage:
tags: tags:
- veloren-docker - veloren-docker
script: script:
- cargo tarpaulin -v - 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"
allow_failure: true - 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: benchmarks:
stage: post stage: post
@ -121,7 +121,18 @@ benchmarks:
script: script:
- unset DISABLE_GIT_LFS_CHECK - unset DISABLE_GIT_LFS_CHECK
- cargo bench - cargo bench
localization-status:
variables:
GIT_DEPTH: 0
stage: post
when: delayed
start_in: 5 seconds
allow_failure: true allow_failure: true
tags:
- veloren-docker
script:
- cargo test -q test_all_localizations -- --nocapture --ignored
linux: linux:
stage: post stage: post
@ -170,5 +181,4 @@ windows:
- assets/ - assets/
- LICENSE - LICENSE
expire_in: 1 week expire_in: 1 week
# -- # --

View File

@ -10,9 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added music system - 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 ### 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 ### Removed
## [0.5.0] - 2019-01-31 ## [0.5.0] - 2019-01-31

177
Cargo.lock generated
View File

@ -139,6 +139,11 @@ name = "autocfg"
version = "0.1.7" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.40" version = "0.3.40"
@ -324,6 +329,10 @@ dependencies = [
name = "cc" name = "cc"
version = "1.0.47" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "cexpr" name = "cexpr"
@ -391,11 +400,6 @@ dependencies = [
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "2.2.0" version = "2.2.0"
@ -1143,6 +1147,20 @@ dependencies = [
"pkg-config 0.3.17 (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 = "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]] [[package]]
name = "gl_generator" name = "gl_generator"
version = "0.11.0" version = "0.11.0"
@ -1399,6 +1417,16 @@ dependencies = [
"unicode-normalization 0.1.11 (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 = "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]] [[package]]
name = "image" name = "image"
version = "0.22.3" version = "0.22.3"
@ -1475,6 +1503,14 @@ name = "itoa"
version = "0.4.4" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "jpeg-decoder" name = "jpeg-decoder"
version = "0.1.16" version = "0.1.16"
@ -1523,6 +1559,19 @@ name = "libc"
version = "0.2.65" version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "libloading" name = "libloading"
version = "0.5.2" version = "0.5.2"
@ -1532,6 +1581,30 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "line_drawing" name = "line_drawing"
version = "0.7.0" version = "0.7.0"
@ -1583,14 +1656,6 @@ name = "lzw"
version = "0.10.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -1666,23 +1731,6 @@ dependencies = [
"x11-dl 2.18.4 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "mio" name = "mio"
version = "0.6.21" version = "0.6.21"
@ -1961,6 +2009,23 @@ dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "orbclient" name = "orbclient"
version = "0.3.27" version = "0.3.27"
@ -2555,15 +2620,13 @@ dependencies = [
[[package]] [[package]]
name = "rodio" name = "rodio"
version = "0.9.0" version = "0.10.0"
source = "git+https://github.com/RustAudio/rodio?rev=e5474a2#e5474a2ef15f2d0a3bae2538de159b6d3e5bdf79" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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)", "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)", "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)", "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)", "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]] [[package]]
@ -2816,16 +2879,6 @@ name = "slab"
version = "0.4.2" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "smallvec" name = "smallvec"
version = "0.6.13" version = "0.6.13"
@ -3154,6 +3207,16 @@ dependencies = [
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "uvth" name = "uvth"
version = "3.1.1" version = "3.1.1"
@ -3164,6 +3227,11 @@ dependencies = [
"num_cpus 1.11.1 (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 = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "vek" name = "vek"
version = "0.9.11" version = "0.9.11"
@ -3291,6 +3359,7 @@ dependencies = [
"gfx 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)", "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_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)", "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)", "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)", "glutin 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
"guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere)", "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)", "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)", "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)", "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)", "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)", "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)", "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 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 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 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 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 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" "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 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 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 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 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 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" "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 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 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 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.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 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" "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 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 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.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 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 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" "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 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 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 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 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 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" "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 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 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 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 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 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 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" "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 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 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 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 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 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" "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 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 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)" = "<none>" "checksum minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)" = "<none>"
"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 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 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" "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-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 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 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 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 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" "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-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 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 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)" = "<none>" "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 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 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" "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 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 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 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 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 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" "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.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 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 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 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 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 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" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

@ -19,6 +19,7 @@
"Colborn", "Colborn",
"Dagfinn", "Dagfinn",
"Dagrod", "Dagrod",
"Digbod",
"Dimian", "Dimian",
"Domnhar", "Domnhar",
"Ebraheim", "Ebraheim",
@ -107,28 +108,34 @@
}, },
"species": { "species": {
"danari": { "danari": {
"keyword": "danari",
"generic": "Danari" "generic": "Danari"
}, },
"dwarf": { "dwarf": {
"keyword": "dwarf",
"generic": "Dwarf" "generic": "Dwarf"
}, },
"elf": { "elf": {
"keyword": "elf",
"generic": "Elf" "generic": "Elf"
}, },
"human": { "human": {
"keyword": "human",
"generic": "Human" "generic": "Human"
}, },
"orc": { "orc": {
"keyword": "orc",
"generic": "Orc" "generic": "Orc"
}, },
"undead": { "undead": {
"keyword": "undead",
"generic": "Undead" "generic": "Undead"
} }
} }
}, },
"quadruped_medium": { "quadruped_medium": {
"body": { "body": {
"keyword": "wolf", "keyword": "quadruped_medium",
"names": [ "names": [
"Achak", "Achak",
"Adalwolf", "Adalwolf",
@ -237,34 +244,42 @@
}, },
"species": { "species": {
"wolf": { "wolf": {
"keyword": "wolf",
"generic": "Wolf" "generic": "Wolf"
}, },
"saber": { "saber": {
"keyword": "sabertooth",
"generic": "Sabertooth Tiger" "generic": "Sabertooth Tiger"
}, },
"viper": { "viper": {
"keyword": "viper",
"generic": "Lizard" "generic": "Lizard"
}, },
"tuskram": { "tuskram": {
"keyword": "tuskram",
"generic": "Tusk Ram" "generic": "Tusk Ram"
}, },
"alligator": { "alligator": {
"keyword": "alligator",
"generic": "Alligator" "generic": "Alligator"
}, },
"monitor": { "monitor": {
"keyword": "monitor",
"generic": "Monitor Lizard" "generic": "Monitor Lizard"
}, },
"lion": { "lion": {
"keyword": "lion",
"generic": "Lion" "generic": "Lion"
}, },
"tarasque": { "tarasque": {
"keyword": "tarasque",
"generic": "Tarasque" "generic": "Tarasque"
} }
} }
}, },
"quadruped_small": { "quadruped_small": {
"body": { "body": {
"keyword": "pig", "keyword": "quadruped_small",
"names": [ "names": [
"Acorn", "Acorn",
"Adeline", "Adeline",
@ -372,102 +387,125 @@
}, },
"species": { "species": {
"pig": { "pig": {
"keyword": "pig",
"generic": "Pig" "generic": "Pig"
}, },
"fox": { "fox": {
"keyword": "fox",
"generic": "Fox" "generic": "Fox"
}, },
"sheep": { "sheep": {
"keyword": "sheep",
"generic": "Sheep" "generic": "Sheep"
}, },
"boar": { "boar": {
"keyword": "boar",
"generic": "Boar" "generic": "Boar"
}, },
"jackalope": { "jackalope": {
"keyword": "jackalope",
"generic": "Jackalope" "generic": "Jackalope"
}, },
"skunk": { "skunk": {
"keyword": "skunk",
"generic": "Skunk" "generic": "Skunk"
}, },
"cat": { "cat": {
"keyword": "cat",
"generic": "Cat" "generic": "Cat"
}, },
"batfox": { "batfox": {
"keyword": "batfox",
"generic": "Bat Fox" "generic": "Bat Fox"
}, },
"raccoon": { "raccoon": {
"keyword": "raccoon",
"generic": "Raccoon" "generic": "Raccoon"
}, },
"quokka": { "quokka": {
"keyword": "quokka",
"generic": "Quokka" "generic": "Quokka"
}, },
"dodarock": { "dodarock": {
"keyword": "dodarock",
"generic": "Dodarock" "generic": "Dodarock"
}, },
"holladon": { "holladon": {
"keyword": "holladon",
"generic": "Holladon" "generic": "Holladon"
} }
} }
}, },
"bird_medium": { "bird_medium": {
"body": { "body": {
"keyword": "duck", "keyword": "bird_medium",
"names": [ "names": [
"Donald" "Donald"
] ]
}, },
"species": { "species": {
"duck": { "duck": {
"keyword": "duck",
"generic": "Duck" "generic": "Duck"
}, },
"chicken": { "chicken": {
"keyword": "chicken",
"generic": "Chicken" "generic": "Chicken"
}, },
"goose": { "goose": {
"keyword": "goose",
"generic": "Goose" "generic": "Goose"
}, },
"peacock": { "peacock": {
"keyword": "peacock",
"generic": "Peacock" "generic": "Peacock"
} }
} }
}, },
"biped_large": { "biped_large": {
"body": { "body": {
"keyword": "giant", "keyword": "biped_large",
"names": [ "names": [
"Leroy Brown" "Leroy Brown"
] ]
}, },
"species": { "species": {
"giant": { "giant": {
"keyword": "giant",
"generic": "Giant" "generic": "Giant"
} }
} }
}, },
"critter": { "critter": {
"body": { "body": {
"keyword": "rat", "keyword": "critter",
"names": [ "names": [
"Remy" "Remy"
] ]
}, },
"species": { "species": {
"rat": { "rat": {
"keyword": "rat",
"generic": "Rat" "generic": "Rat"
}, },
"axolotl": { "axolotl": {
"keyword": "axolotl",
"generic": "Axolotl" "generic": "Axolotl"
}, },
"gecko": { "gecko": {
"keyword": "gecko",
"generic": "Gecko" "generic": "Gecko"
}, },
"turtle": { "turtle": {
"keyword": "turtle",
"generic": "Turtle" "generic": "Turtle"
}, },
"squirrel": { "squirrel": {
"keyword": "squirrel",
"generic": "Squirrel" "generic": "Squirrel"
}, },
"fungome": { "fungome": {
"keyword": "fungome",
"generic": "Fungome" "generic": "Fungome"
} }
} }

View File

@ -23,5 +23,17 @@
], ],
threshold: 0.5, 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,
),
} }
) )

BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/button.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/button_hover.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/buttons/button_press.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/indicator_mmap_small.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/banner.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/frames/banner_small_top.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/banner_top.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/enemybar.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/frames/enemybar_bg.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/esc_menu.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/frames/mmap.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/icons/elf_m.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/misc_bg/textbox_bot.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/misc_bg/textbox_mid.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/misc_bg/textbox_top.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -17,6 +17,28 @@ VoxygenLocalization(
language_identifier: "en", language_identifier: "en",
), ),
convert_utf8_to_ascii: false, 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: { string_map: {
/// Start Common section /// Start Common section
// Texts used in multiple locations with the same formatting // 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.view_distance": "View Distance",
"hud.settings.maximum_fps": "Maximum FPS", "hud.settings.maximum_fps": "Maximum FPS",
"hud.settings.fov": "Field of View (deg)", "hud.settings.fov": "Field of View (deg)",
"hud.settings.gamma": "Gamma",
"hud.settings.antialiasing_mode": "AntiAliasing Mode", "hud.settings.antialiasing_mode": "AntiAliasing Mode",
"hud.settings.cloud_rendering_mode": "Cloud Rendering Mode", "hud.settings.cloud_rendering_mode": "Cloud Rendering Mode",
"hud.settings.fluid_rendering_mode": "Fluid Rendering Mode", "hud.settings.fluid_rendering_mode": "Fluid Rendering Mode",

View File

@ -4,7 +4,29 @@ VoxygenLocalization(
language_name: "Français", language_name: "Français",
language_identifier: "fr_FR", 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: { string_map: {
// Common texts used in multiple locations // Common texts used in multiple locations
"common.username": "pseudo", "common.username": "pseudo",
@ -16,7 +38,7 @@ VoxygenLocalization(
"common.languages": "Langues", "common.languages": "Langues",
"common.interface": "Interface", "common.interface": "Interface",
"common.gameplay": "Gameplay", "common.gameplay": "Gameplay",
"common.controls": "Controles", "common.controls": "Contrôles",
"common.video": "Video", "common.video": "Video",
"common.sound": "Audio", "common.sound": "Audio",
"common.resume": "Reprendre", "common.resume": "Reprendre",

View File

@ -3,9 +3,9 @@ uniform sampler2D t_noise;
const float CLOUD_AVG_HEIGHT = 1025.0; const float CLOUD_AVG_HEIGHT = 1025.0;
const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0; const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0;
const float CLOUD_HEIGHT_MAX = 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_SCALE = 5.0;
const float CLOUD_DENSITY = 80.0; const float CLOUD_DENSITY = 100.0;
float vsum(vec3 v) { float vsum(vec3 v) {
return v.x + v.y + v.z; return v.x + v.y + v.z;
@ -21,22 +21,27 @@ vec2 cloud_at(vec3 pos) {
float value = ( float value = (
0.0 0.0
+ texture(t_noise, scaled_pos * 0.0003 + tick_offs).x + 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.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.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.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.02 + tick_offs + time_of_day.x * 0.0004).x * 0.2
) / 3.0; ) / 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; 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); const float SHADE_GRADIENT = 1.5 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN);
float shade = ((pos.z - CLOUD_AVG_HEIGHT) * SHADE_GRADIENT + 0.5); 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)); 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) { 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; const float INCR = 1.0 / ITERS;
float mind = (CLOUD_HEIGHT_MIN - origin.z) / dir.z; 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; passthrough *= 1.0 - integral;
cloud_shade = mix(cloud_shade, sample.x, passthrough * integral); cloud_shade = mix(cloud_shade, sample.x, passthrough * integral);
dist += INCR * delta; dist += INCR * delta;
if (passthrough < 0.05 || (passthrough > 0.8 && dist > (maxd + mind) * 0.7)) {
break;
}
} }
} }

View File

@ -12,4 +12,5 @@ uniform u_globals {
uvec4 light_shadow_count; uvec4 light_shadow_count;
uvec4 medium; uvec4 medium;
ivec4 select_pos; ivec4 select_pos;
vec4 gamma;
}; };

View File

@ -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 sun_light = get_sun_brightness(sun_dir);
float moon_light = get_moon_brightness(moon_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 sun_color = get_sun_color(sun_dir);
vec3 moon_color = get_moon_color(moon_dir); vec3 moon_color = get_moon_color(moon_dir);
vec3 sun_chroma = sun_color * sun_light; 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_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_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 // 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
clouds = get_cloud_color(dir, origin, time_of_day, f_dist, quality); 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) { if (f_dist > 5000.0) {
sky_color += sun_light + moon_light; sky_color += sun_light + moon_light;

View File

@ -45,7 +45,7 @@ void main() {
//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0); //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 = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
vec4 final_color = aa_color; vec4 final_color = pow(aa_color, gamma);
if (medium.x == 1u) { if (medium.x == 1u) {
final_color *= vec4(0.2, 0.2, 0.8, 1.0); final_color *= vec4(0.2, 0.2, 0.8, 1.0);

View File

@ -20,7 +20,8 @@ void main() {
if (f_mode == uint(0)) { if (f_mode == uint(0)) {
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
// Image // 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); tgt_color = f_color * texture(u_tex, f_uv);
// 2D Geometry // 2D Geometry
} else if (f_mode == uint(2)) { } else if (f_mode == uint(2)) {

View File

@ -4,6 +4,7 @@
in vec2 v_pos; in vec2 v_pos;
in vec2 v_uv; in vec2 v_uv;
in vec2 v_center;
in vec4 v_color; in vec4 v_color;
in uint v_mode; in uint v_mode;
@ -19,15 +20,31 @@ flat out uint f_mode;
out vec4 f_color; out vec4 f_color;
void main() { void main() {
f_uv = v_uv;
f_color = v_color; f_color = v_color;
if (w_pos.w == 1.0) { if (w_pos.w == 1.0) {
f_uv = v_uv;
// Fixed scale In-game element // Fixed scale In-game element
vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0); 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); 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 { } else {
// Interface element // Interface element
f_uv = v_uv;
gl_Position = vec4(v_pos, 0.0, 1.0); gl_Position = vec4(v_pos, 0.0, 1.0);
} }
f_mode = v_mode; f_mode = v_mode;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-5.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-6.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -27,6 +27,7 @@
Some(("figure.hair.human.male-20", (-3, -4, -7))), Some(("figure.hair.human.male-20", (-3, -4, -7))),
], ],
beard: [ beard: [
None,
Some(("figure.beard.human.human-0", (4, 6, -2))), Some(("figure.beard.human.human-0", (4, 6, -2))),
Some(("figure.beard.human.human-1", (5, 10, -2))), Some(("figure.beard.human.human-1", (5, 10, -2))),
Some(("figure.beard.human.human-2", (3, 7, -3))), Some(("figure.beard.human.human-2", (3, 7, -3))),
@ -92,17 +93,24 @@
], ],
), ),
(Orc, Female): ( (Orc, Female): (
offset: (-8.0, -3.0, -6.0), offset: (-8.0, -2.5, -6.0),
head: ("figure.head.orc.female", (0, 2, 0)), head: ("figure.head.orc.female", (0, 1, 0)),
eyes: ("figure.eyes.orc.female-0", (3, 9, 2)), eyes: ("figure.eyes.orc.female-0", (3, 8, 2)),
hair: [ 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], beard: [None],
accessory: [ accessory: [
None, None,
Some(("figure.accessory.orc.earring-female-0", (2, 5, 1))), Some(("figure.accessory.orc.earring-female-0", (2, 4, 1))),
Some(("figure.accessory.orc.warpaint-female-0", (3, 5, 1))), Some(("figure.accessory.orc.warpaint-female-0", (-2, -4, -7))),
Some(("figure.accessory.orc.warpaint-female-1", (-2, -4, -7))),
], ],
), ),
(Elf, Male): ( (Elf, Male): (
@ -239,6 +247,7 @@
Some(("figure.hair.danari.male-1", (3, 1, 2))), Some(("figure.hair.danari.male-1", (3, 1, 2))),
], ],
beard: [ beard: [
None,
Some(("figure.beard.danari.danari-0", (4, 6, -1))), Some(("figure.beard.danari.danari-0", (4, 6, -1))),
], ],
accessory: [ accessory: [

Binary file not shown.

View File

@ -8,6 +8,8 @@ use std::{
fn main() { fn main() {
// Get the current githash // 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") match Command::new("git")
.args(&[ .args(&[
"log", "log",
@ -15,6 +17,7 @@ fn main() {
"1", "1",
"--pretty=format:%h/%cd", "--pretty=format:%h/%cd",
"--date=format:%Y-%m-%d-%H:%M", "--date=format:%Y-%m-%d-%H:%M",
"--abbrev=8",
]) ])
.output() .output()
{ {

View File

@ -63,6 +63,7 @@ pub struct AllBodies<BodyMeta, SpeciesMeta> {
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> { impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
type Output = BodyMeta; type Output = BodyMeta;
#[inline]
fn index(&self, index: NpcKind) -> &Self::Output { fn index(&self, index: NpcKind) -> &Self::Output {
match index { match index {
NpcKind::Humanoid => &self.humanoid.body, 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) // TODO: Improve these values (some might be reliant on more info in inner type)
match self { match self {
Body::Humanoid(_) => 0.5, Body::Humanoid(_) => 0.5,
Body::QuadrupedSmall(_) => 0.6, Body::QuadrupedSmall(_) => 0.3,
Body::QuadrupedMedium(_) => 0.9, Body::QuadrupedMedium(_) => 0.9,
Body::Critter(_) => 0.5, Body::Critter(_) => 0.2,
Body::BirdMedium(_) => 0.5, Body::BirdMedium(_) => 0.5,
Body::FishMedium(_) => 0.5, Body::FishMedium(_) => 0.5,
Body::Dragon(_) => 2.5, Body::Dragon(_) => 2.5,
@ -113,6 +114,9 @@ impl Body {
Body::Object(_) => 0.3, Body::Object(_) => 0.3,
} }
} }
// Note: currently assumes sphericality
pub fn height(&self) -> f32 { self.radius() * 2.0 }
} }
impl Component for Body { impl Component for Body {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); 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 } Self { species, body_type }
} }
} }
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::BipedLarge(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Species { pub enum Species {
@ -29,10 +38,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub giant: SpeciesMeta, pub giant: SpeciesMeta,
} }
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> { impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta; type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output { #[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index { match index {
Species::Giant => &self.giant, Species::Giant => &self.giant,
} }
@ -41,6 +51,14 @@ impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> {
pub const ALL_SPECIES: [Species; 1] = [Species::Giant]; pub const ALL_SPECIES: [Species; 1] = [Species::Giant];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum BodyType { pub enum BodyType {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); 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 } Self { species, body_type }
} }
} }
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::BirdMedium(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Species { pub enum Species {
@ -35,10 +44,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub peacock: SpeciesMeta, pub peacock: SpeciesMeta,
} }
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> { impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta; type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output { #[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index { match index {
Species::Duck => &self.duck, Species::Duck => &self.duck,
Species::Chicken => &self.chicken, Species::Chicken => &self.chicken,
@ -55,6 +65,14 @@ pub const ALL_SPECIES: [Species; 4] = [
Species::Peacock, Species::Peacock,
]; ];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum BodyType { pub enum BodyType {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); 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 } Self { species, body_type }
} }
} }
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::Critter(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Species { pub enum Species {
@ -37,10 +46,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub fungome: SpeciesMeta, pub fungome: SpeciesMeta,
} }
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> { impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta; type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output { #[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index { match index {
Species::Rat => &self.rat, Species::Rat => &self.rat,
Species::Axolotl => &self.axolotl, Species::Axolotl => &self.axolotl,
@ -61,6 +71,14 @@ pub const ALL_SPECIES: [Species; 6] = [
Species::Fungome, Species::Fungome,
]; ];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum BodyType { pub enum BodyType {

View File

@ -24,19 +24,24 @@ impl Body {
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let race = *(&ALL_RACES).choose(&mut rng).unwrap(); 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 { Self {
race, race,
body_type, body_type,
chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(), chest: *(&ALL_CHESTS).choose(rng).unwrap(),
belt: *(&ALL_BELTS).choose(&mut rng).unwrap(), belt: *(&ALL_BELTS).choose(rng).unwrap(),
pants: *(&ALL_PANTS).choose(&mut rng).unwrap(), pants: *(&ALL_PANTS).choose(rng).unwrap(),
hand: *(&ALL_HANDS).choose(&mut rng).unwrap(), hand: *(&ALL_HANDS).choose(rng).unwrap(),
foot: *(&ALL_FEET).choose(&mut rng).unwrap(), foot: *(&ALL_FEET).choose(rng).unwrap(),
shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(), shoulder: *(&ALL_SHOULDERS).choose(rng).unwrap(),
hair_style: rng.gen_range(0, race.num_hair_styles(body_type)), hair_style: rng.gen_range(0, race.num_hair_styles(body_type)),
beard: rng.gen_range(0, race.num_beards(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)), accessory: rng.gen_range(0, race.num_accessories(body_type)),
hair_color: rng.gen_range(0, race.num_hair_colors()) as u8, hair_color: rng.gen_range(0, race.num_hair_colors()) as u8,
skin: rng.gen_range(0, race.num_skin_colors()) as u8, skin: rng.gen_range(0, race.num_skin_colors()) as u8,
@ -58,6 +63,10 @@ impl Body {
} }
} }
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::Humanoid(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Race { pub enum Race {
@ -82,10 +91,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub undead: SpeciesMeta, pub undead: SpeciesMeta,
} }
impl<SpeciesMeta> core::ops::Index<Race> for AllSpecies<SpeciesMeta> { impl<'a, SpeciesMeta> core::ops::Index<&'a Race> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta; type Output = SpeciesMeta;
fn index(&self, index: Race) -> &Self::Output { #[inline]
fn index(&self, &index: &'a Race) -> &Self::Output {
match index { match index {
Race::Danari => &self.danari, Race::Danari => &self.danari,
Race::Dwarf => &self.dwarf, Race::Dwarf => &self.dwarf,
@ -106,6 +116,14 @@ pub const ALL_RACES: [Race; 6] = [
Race::Undead, Race::Undead,
]; ];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Race;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_RACES.iter().copied() }
}
// Hair Colors // Hair Colors
pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [ pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [
(198, 169, 113), // Philosopher's Grey (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, 208, 147), // Gold Blonde
//(228, 223, 141), // Platinum Blonde //(228, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde (199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown (107, 76, 51), // Oak Skin4
//(203, 154, 98), // Light Brown //(203, 154, 98), // Light Skin4
(64, 32, 18), // Chocolate Brown (64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Brown (86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black (57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple (101, 83, 95), // Matte Purple
(101, 57, 90), // Witch 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, 208, 147), // Gold Blonde
(228, 223, 141), // Platinum Blonde (228, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde (199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown (107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Brown (203, 154, 98), // Light Skin4
(64, 32, 18), // Chocolate Brown (64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Brown (86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black (57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple (101, 83, 95), // Matte Purple
(101, 57, 90), // Witch 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, 208, 147), // Gold Blonde
(228, 223, 141), // Platinum Blonde (228, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde (199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown (107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Brown (203, 154, 98), // Light Skin4
(64, 32, 18), // Chocolate Brown (64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Brown (86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black (57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple (101, 83, 95), // Matte Purple
(101, 57, 90), // Witch 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, 208, 147), // Gold Blonde
(228, 223, 141), // Platinum Blonde (228, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde (199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown (107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Brown (203, 154, 98), // Light Skin4
(64, 32, 18), // Chocolate Brown (64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Brown (86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black (57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple (101, 83, 95), // Matte Purple
(101, 57, 90), // Witch 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] = [ pub const ORC_HAIR_COLORS: [(u8, u8, u8); 10] = [
(66, 66, 59), // Wise Grey (66, 66, 59), // Wise Grey
//(107, 76, 51), // Oak Brown //(107, 76, 51), // Oak Skin4
//(203, 154, 98), // Light Brown //(203, 154, 98), // Light Skin4
(64, 32, 18), // Chocolate Brown (64, 32, 18), // Skin7 Skin4
(54, 30, 26), // Dark Chocolate (54, 30, 26), // Dark Skin7
(86, 72, 71), // Ash Brown (86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black (57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple (101, 83, 95), // Matte Purple
(101, 57, 90), // Witch 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, 208, 147), // Gold Blonde
//(228, 223, 141), // Platinum Blonde //(228, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde (199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown (107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Brown (203, 154, 98), // Light Skin4
(64, 32, 18), // Chocolate Brown (64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Brown (86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black (57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple (101, 83, 95), // Matte Purple
(101, 57, 90), // Witch Purple (101, 57, 90), // Witch Purple
@ -243,30 +261,59 @@ pub const DANARI_SKIN_COLORS: [Skin; 4] = [
Skin::DanariThree, Skin::DanariThree,
Skin::DanariFour, Skin::DanariFour,
]; ];
pub const DWARF_SKIN_COLORS: [Skin; 5] = [ pub const DWARF_SKIN_COLORS: [Skin; 14] = [
Skin::Pale, Skin::Skin1,
Skin::White, Skin::Skin2,
Skin::Tanned, Skin::Skin3,
Skin::Skin4,
Skin::Skin5,
Skin::Skin6,
Skin::Skin7,
Skin::Skin8,
Skin::Skin9,
Skin::Skin10,
Skin::Skin11,
Skin::Skin12,
Skin::Iron, Skin::Iron,
Skin::Steel, Skin::Steel,
]; ];
pub const ELF_SKIN_COLORS: [Skin; 7] = [ pub const ELF_SKIN_COLORS: [Skin; 14] = [
Skin::Pale, 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::ElfOne,
Skin::ElfTwo, Skin::ElfTwo,
Skin::ElfThree, Skin::ElfThree,
Skin::White,
Skin::Tanned,
Skin::TannedBrown,
]; ];
pub const HUMAN_SKIN_COLORS: [Skin; 5] = [ pub const HUMAN_SKIN_COLORS: [Skin; 18] = [
Skin::Pale, Skin::Skin1,
Skin::White, Skin::Skin2,
Skin::Tanned, Skin::Skin3,
Skin::TannedBrown, Skin::Skin4,
Skin::TannedDarkBrown, 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]; pub const UNDEAD_SKIN_COLORS: [Skin; 3] = [Skin::UndeadOne, Skin::UndeadTwo, Skin::UndeadThree];
// Eye colors // Eye colors
@ -275,22 +322,31 @@ pub const DANARI_EYE_COLORS: [EyeColor; 3] = [
EyeColor::LoyalBrown, EyeColor::LoyalBrown,
EyeColor::ViciousRed, EyeColor::ViciousRed,
]; ];
pub const DWARF_EYE_COLORS: [EyeColor; 3] = [ pub const DWARF_EYE_COLORS: [EyeColor; 4] = [
EyeColor::CuriousGreen, EyeColor::CuriousGreen,
EyeColor::LoyalBrown, EyeColor::LoyalBrown,
EyeColor::NobleBlue, EyeColor::NobleBlue,
EyeColor::CornflowerBlue,
]; ];
pub const ELF_EYE_COLORS: [EyeColor; 3] = [ pub const ELF_EYE_COLORS: [EyeColor; 4] = [
EyeColor::NobleBlue, EyeColor::NobleBlue,
EyeColor::CornflowerBlue,
EyeColor::CuriousGreen, EyeColor::CuriousGreen,
EyeColor::LoyalBrown, EyeColor::LoyalBrown,
]; ];
pub const HUMAN_EYE_COLORS: [EyeColor; 3] = [ pub const HUMAN_EYE_COLORS: [EyeColor; 4] = [
EyeColor::NobleBlue, EyeColor::NobleBlue,
EyeColor::CornflowerBlue,
EyeColor::CuriousGreen, EyeColor::CuriousGreen,
EyeColor::LoyalBrown, 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] = [ pub const UNDEAD_EYE_COLORS: [EyeColor; 5] = [
EyeColor::ViciousRed, EyeColor::ViciousRed,
EyeColor::PumpkinOrange, EyeColor::PumpkinOrange,
@ -347,7 +403,7 @@ impl Race {
self.skin_colors() self.skin_colors()
.get(val as usize) .get(val as usize)
.copied() .copied()
.unwrap_or(Skin::Tanned) .unwrap_or(Skin::Skin3)
} }
pub fn num_skin_colors(self) -> u8 { self.skin_colors().len() as u8 } 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::Elf, BodyType::Male) => 4,
(Race::Human, BodyType::Female) => 19, (Race::Human, BodyType::Female) => 19,
(Race::Human, BodyType::Male) => 17, (Race::Human, BodyType::Male) => 17,
(Race::Orc, BodyType::Female) => 1, (Race::Orc, BodyType::Female) => 7,
(Race::Orc, BodyType::Male) => 8, (Race::Orc, BodyType::Male) => 8,
(Race::Undead, BodyType::Female) => 4, (Race::Undead, BodyType::Female) => 4,
(Race::Undead, BodyType::Male) => 3, (Race::Undead, BodyType::Male) => 3,
@ -388,7 +444,7 @@ impl Race {
(Race::Elf, BodyType::Male) => 1, (Race::Elf, BodyType::Male) => 1,
(Race::Human, BodyType::Female) => 1, (Race::Human, BodyType::Female) => 1,
(Race::Human, BodyType::Male) => 1, (Race::Human, BodyType::Male) => 1,
(Race::Orc, BodyType::Female) => 3, (Race::Orc, BodyType::Female) => 4,
(Race::Orc, BodyType::Male) => 5, (Race::Orc, BodyType::Male) => 5,
(Race::Undead, BodyType::Female) => 1, (Race::Undead, BodyType::Female) => 1,
(Race::Undead, BodyType::Male) => 1, (Race::Undead, BodyType::Male) => 1,
@ -516,6 +572,10 @@ pub enum EyeColor {
MagicPurple = 7, MagicPurple = 7,
ToxicGreen = 8, ToxicGreen = 8,
ExoticPurple = 9, ExoticPurple = 9,
SulfurYellow = 10,
AmberOrange = 11,
PineGreen = 12,
CornflowerBlue = 13,
} }
impl EyeColor { impl EyeColor {
pub fn light_rgb(self) -> Rgb<u8> { pub fn light_rgb(self) -> Rgb<u8> {
@ -530,6 +590,10 @@ impl EyeColor {
EyeColor::MagicPurple => Rgb::new(137, 4, 177), EyeColor::MagicPurple => Rgb::new(137, 4, 177),
EyeColor::ToxicGreen => Rgb::new(1, 223, 1), EyeColor::ToxicGreen => Rgb::new(1, 223, 1),
EyeColor::ExoticPurple => Rgb::new(95, 32, 111), 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::MagicPurple => Rgb::new(110, 3, 143),
EyeColor::ToxicGreen => Rgb::new(1, 185, 1), EyeColor::ToxicGreen => Rgb::new(1, 185, 1),
EyeColor::ExoticPurple => Rgb::new(69, 23, 80), 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Skin { pub enum Skin {
Pale = 0, Skin1 = 0,
White = 1, Skin2 = 1,
Tanned = 2, Skin3 = 2,
Brown = 3, Skin4 = 3,
TannedBrown = 4, Skin5 = 4,
TannedDarkBrown = 5, Skin6 = 5,
Iron = 6, Iron = 6,
Steel = 7, Steel = 7,
DanariOne = 8, DanariOne = 8,
@ -583,16 +651,41 @@ pub enum Skin {
UndeadOne = 18, UndeadOne = 18,
UndeadTwo = 19, UndeadTwo = 19,
UndeadThree = 20, 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 { impl Skin {
pub fn rgb(self) -> Rgb<u8> { pub fn rgb(self) -> Rgb<u8> {
let color = match self { let color = match self {
Self::Pale => (252, 211, 179), Self::Skin1 => (255, 229, 200),
Self::White => (253, 195, 164), Self::Skin2 => (255, 218, 190),
Self::Tanned => (222, 181, 151), Self::Skin3 => (255, 206, 180),
Self::Brown => (123, 80, 45), Self::Skin4 => (255, 195, 170),
Self::TannedBrown => (135, 70, 50), Self::Skin5 => (240, 184, 160),
Self::TannedDarkBrown => (116, 61, 43), 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::Iron => (135, 113, 95),
Self::Steel => (108, 94, 86), Self::Steel => (108, 94, 86),
Self::DanariOne => (104, 168, 196), Self::DanariOne => (104, 168, 196),
@ -605,6 +698,7 @@ impl Skin {
Self::OrcOne => (61, 130, 42), Self::OrcOne => (61, 130, 42),
Self::OrcTwo => (82, 117, 36), Self::OrcTwo => (82, 117, 36),
Self::OrcThree => (71, 94, 42), Self::OrcThree => (71, 94, 42),
Self::OrcFour => (97, 54, 29),
Self::UndeadOne => (240, 243, 239), Self::UndeadOne => (240, 243, 239),
Self::UndeadTwo => (178, 178, 178), Self::UndeadTwo => (178, 178, 178),
Self::UndeadThree => (145, 135, 121), Self::UndeadThree => (145, 135, 121),
@ -614,12 +708,24 @@ impl Skin {
pub fn light_rgb(self) -> Rgb<u8> { pub fn light_rgb(self) -> Rgb<u8> {
let color = match self { let color = match self {
Self::Pale => (255, 227, 193), Self::Skin1 => (255, 229, 200),
Self::White => (255, 210, 180), Self::Skin2 => (255, 218, 190),
Self::Tanned => (239, 197, 164), Self::Skin3 => (255, 206, 180),
Self::Brown => (150, 104, 68), Self::Skin4 => (255, 195, 170),
Self::TannedBrown => (148, 85, 64), Self::Skin5 => (240, 184, 160),
Self::TannedDarkBrown => (132, 74, 56), 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::Iron => (144, 125, 106),
Self::Steel => (120, 107, 99), Self::Steel => (120, 107, 99),
Self::DanariOne => (116, 176, 208), Self::DanariOne => (116, 176, 208),
@ -632,6 +738,7 @@ impl Skin {
Self::OrcOne => (83, 165, 56), Self::OrcOne => (83, 165, 56),
Self::OrcTwo => (92, 132, 46), Self::OrcTwo => (92, 132, 46),
Self::OrcThree => (84, 110, 54), Self::OrcThree => (84, 110, 54),
Self::OrcFour => (97, 54, 29),
Self::UndeadOne => (254, 252, 251), Self::UndeadOne => (254, 252, 251),
Self::UndeadTwo => (190, 192, 191), Self::UndeadTwo => (190, 192, 191),
Self::UndeadThree => (160, 151, 134), Self::UndeadThree => (160, 151, 134),
@ -641,12 +748,24 @@ impl Skin {
pub fn dark_rgb(self) -> Rgb<u8> { pub fn dark_rgb(self) -> Rgb<u8> {
let color = match self { let color = match self {
Self::Pale => (229, 192, 163), Self::Skin1 => (242, 217, 189),
Self::White => (239, 179, 150), Self::Skin2 => (242, 207, 189),
Self::Tanned => (208, 167, 135), Self::Skin3 => (242, 197, 172),
Self::Brown => (106, 63, 30), Self::Skin4 => (242, 186, 162),
Self::TannedBrown => (122, 58, 40), Self::Skin5 => (212, 173, 150),
Self::TannedDarkBrown => (100, 47, 32), 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::Iron => (124, 99, 82),
Self::Steel => (96, 81, 72), Self::Steel => (96, 81, 72),
Self::DanariOne => (92, 155, 183), Self::DanariOne => (92, 155, 183),
@ -659,6 +778,7 @@ impl Skin {
Self::OrcOne => (55, 114, 36), Self::OrcOne => (55, 114, 36),
Self::OrcTwo => (70, 104, 29), Self::OrcTwo => (70, 104, 29),
Self::OrcThree => (60, 83, 32), Self::OrcThree => (60, 83, 32),
Self::OrcFour => (84, 47, 25),
Self::UndeadOne => (229, 231, 230), Self::UndeadOne => (229, 231, 230),
Self::UndeadTwo => (165, 166, 164), Self::UndeadTwo => (165, 166, 164),
Self::UndeadThree => (130, 122, 106), Self::UndeadThree => (130, 122, 106),

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); 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 } Self { species, body_type }
} }
} }
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::QuadrupedMedium(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Species { pub enum Species {
@ -43,10 +52,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub tarasque: SpeciesMeta, pub tarasque: SpeciesMeta,
} }
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> { impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta; type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output { #[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index { match index {
Species::Wolf => &self.wolf, Species::Wolf => &self.wolf,
Species::Saber => &self.saber, Species::Saber => &self.saber,
@ -71,6 +81,14 @@ pub const ALL_SPECIES: [Species; 8] = [
Species::Tarasque, Species::Tarasque,
]; ];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum BodyType { pub enum BodyType {

View File

@ -10,11 +10,20 @@ impl Body {
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let species = *(&ALL_SPECIES).choose(&mut rng).unwrap(); 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 } Self { species, body_type }
} }
} }
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::QuadrupedSmall(body) }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum Species { pub enum Species {
@ -51,10 +60,11 @@ pub struct AllSpecies<SpeciesMeta> {
pub holladon: SpeciesMeta, pub holladon: SpeciesMeta,
} }
impl<SpeciesMeta> core::ops::Index<Species> for AllSpecies<SpeciesMeta> { impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
type Output = SpeciesMeta; type Output = SpeciesMeta;
fn index(&self, index: Species) -> &Self::Output { #[inline]
fn index(&self, &index: &'a Species) -> &Self::Output {
match index { match index {
Species::Pig => &self.pig, Species::Pig => &self.pig,
Species::Fox => &self.fox, Species::Fox => &self.fox,
@ -87,6 +97,14 @@ pub const ALL_SPECIES: [Species; 12] = [
Species::Holladon, Species::Holladon,
]; ];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
type Item = Species;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)] #[repr(u32)]
pub enum BodyType { pub enum BodyType {

View File

@ -57,12 +57,12 @@ impl Input {
/// Whether it's the first frame this input has been in /// Whether it's the first frame this input has been in
/// its current state /// 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 /// Whether input has been in current state longer than
/// `DEFAULT_HOLD_DURATION` /// `DEFAULT_HOLD_DURATION`
pub fn is_held_down(&self) -> bool { 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` /// Whether input has been pressed for longer than `threshold`

View File

@ -12,11 +12,11 @@ pub struct Energy {
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum EnergySource { pub enum EnergySource {
CastSpell, CastSpell,
Roll,
Climb,
LevelUp, LevelUp,
Regen, Regen,
Revive, Revive,
Climb,
Roll,
Unknown, Unknown,
} }

View File

@ -8,22 +8,26 @@ use vek::*;
pub struct SfxEventItem { pub struct SfxEventItem {
pub sfx: SfxEvent, pub sfx: SfxEvent,
pub pos: Option<Vec3<f32>>, pub pos: Option<Vec3<f32>>,
pub vol: Option<f32>,
} }
impl SfxEventItem { impl SfxEventItem {
pub fn new(sfx: SfxEvent, pos: Option<Vec3<f32>>) -> Self { Self { sfx, pos } } pub fn new(sfx: SfxEvent, pos: Option<Vec3<f32>>, vol: Option<f32>) -> 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)] #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
pub enum SfxEvent { pub enum SfxEvent {
Idle, Idle,
PlaceBlock,
RemoveBlock,
OpenChest,
ChatTellReceived,
OpenBag,
Run, Run,
Roll, Roll,
Climb, Climb,
@ -39,6 +43,8 @@ pub enum SfxEvent {
ExtinguishLantern, ExtinguishLantern,
Attack, Attack,
AttackWolf, AttackWolf,
Wield(comp::item::ToolKind),
Unwield(comp::item::ToolKind),
} }
pub enum LocalEvent { pub enum LocalEvent {

View File

@ -1,6 +1,12 @@
#![deny(unsafe_code)] #![deny(unsafe_code)]
#![type_length_limit = "1664759"] #![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 serde_derive;
#[macro_use] extern crate log; #[macro_use] extern crate log;

View File

@ -384,6 +384,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn send_recv_huge() { fn send_recv_huge() {
let (mut postoffice, sock) = create_postoffice::<(), Vec<i32>>(3).unwrap(); let (mut postoffice, sock) = create_postoffice::<(), Vec<i32>>(3).unwrap();
let test_msgs: Vec<Vec<i32>> = (0..5) let test_msgs: Vec<Vec<i32>> = (0..5)

View File

@ -1,4 +1,7 @@
use crate::{assets, comp::AllBodies}; use crate::{
assets,
comp::{self, AllBodies, Body},
};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use std::{str::FromStr, sync::Arc}; use std::{str::FromStr, sync::Arc};
@ -41,6 +44,10 @@ pub struct BodyNames {
/// NOTE: Deliberately don't (yet?) implement serialize. /// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct SpeciesNames { 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. /// The generic name for NPCs of this species.
pub generic: String, pub generic: String,
} }
@ -56,11 +63,11 @@ impl FromStr for NpcKind {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, ()> { fn from_str(s: &str) -> Result<Self, ()> {
let npc_names_json = &*NPC_NAMES; let npc_names = &*NPC_NAMES;
ALL_NPCS ALL_NPCS
.iter() .iter()
.copied() .copied()
.find(|&npc| npc_names_json[npc].keyword == s) .find(|&npc| npc_names[npc].keyword == s)
.ok_or(()) .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. // If no pretty name is found, fall back to the keyword.
names.choose(&mut rand::thread_rng()).unwrap_or(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<dyn FnMut() -> 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, ()> { 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<Self, ()> {
fn parse<
'a,
B: Into<Body> + '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<BodyMeta, SpeciesData>,
conv_func: for<'d> fn(&mut rand::rngs::ThreadRng, &'d Species) -> B,
) -> Option<NpcBody>
where
&'a SpeciesData: IntoIterator<Item = Species>,
{
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(())
}
}

View File

@ -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) { 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) { if let Some(Tool(_)) = ecs_data.stats.equipment.main.as_ref().map(|i| &i.kind) {
update.character = CharacterState::Wielding(None); update.character = CharacterState::Wielding(None);
} }

View File

@ -13,6 +13,7 @@ use specs::{
}; };
const CHARGE_COST: i32 = 200; const CHARGE_COST: i32 = 200;
const ROLL_COST: i32 = 30;
pub struct Sys; pub struct Sys;

View File

@ -1,8 +1,8 @@
use super::phys::GRAVITY; use super::phys::GRAVITY;
use crate::{ use crate::{
comp::{ comp::{
ActionState, CharacterState, Controller, Mounting, MovementState::*, Ori, PhysicsState, ActionState, CharacterState, Controller, Energy, EnergySource, Mounting, MovementState::*,
Pos, Stats, Vel, Ori, PhysicsState, Pos, Stats, Vel,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
state::DeltaTime, state::DeltaTime,
@ -31,6 +31,7 @@ const BLOCK_SPEED: f32 = 75.0;
// Gravity is 9.81 * 4, so this makes gravity equal to .15 // Gravity is 9.81 * 4, so this makes gravity equal to .15
const GLIDE_ANTIGRAV: f32 = GRAVITY * 0.96; const GLIDE_ANTIGRAV: f32 = GRAVITY * 0.96;
const CLIMB_SPEED: f32 = 5.0; const CLIMB_SPEED: f32 = 5.0;
const CLIMB_COST: i32 = 5;
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
@ -54,6 +55,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
WriteStorage<'a, Energy>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, Controller>, ReadStorage<'a, Controller>,
@ -72,6 +74,7 @@ impl<'a> System<'a> for Sys {
mut positions, mut positions,
mut velocities, mut velocities,
mut orientations, mut orientations,
mut energies,
uids, uids,
stats, stats,
controllers, controllers,
@ -86,6 +89,7 @@ impl<'a> System<'a> for Sys {
mut _pos, mut _pos,
mut vel, mut vel,
mut ori, mut ori,
mut energy,
_uid, _uid,
stats, stats,
controller, controller,
@ -97,6 +101,7 @@ impl<'a> System<'a> for Sys {
&mut positions, &mut positions,
&mut velocities, &mut velocities,
&mut orientations, &mut orientations,
&mut energies.restrict_mut(),
&uids, &uids,
&stats, &stats,
&controllers, &controllers,
@ -228,9 +233,21 @@ impl<'a> System<'a> for Sys {
physics.on_wall, physics.on_wall,
) { ) {
if inputs.climb_down.is_pressed() && !inputs.climb.is_pressed() { 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() { } 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 { } else {
vel.0.z = (vel.0.z - dt.0 * GRAVITY * 0.01).min(CLIMB_SPEED); vel.0.z = (vel.0.z - dt.0 * GRAVITY * 0.01).min(CLIMB_SPEED);
} }

View File

@ -91,7 +91,7 @@ impl<'a> System<'a> for Sys {
(energy.regen_rate + ENERGY_REGEN_ACCEL * dt.0).min(100.0); (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(_) => { CharacterState::Wielded(_) => {
if energy.get_unchecked().regen_rate != 0.0 { if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0 energy.get_mut_unchecked().regen_rate = 0.0

View File

@ -1 +1 @@
nightly-2020-01-18 nightly-2020-02-06

View File

@ -8,7 +8,7 @@ use common::{
assets, comp, assets, comp,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
msg::{PlayerListUpdate, ServerMsg}, msg::{PlayerListUpdate, ServerMsg},
npc::{get_npc_name, NpcKind}, npc::{self, get_npc_name},
state::TimeOfDay, state::TimeOfDay,
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
@ -234,11 +234,18 @@ lazy_static! {
), ),
ChatCommand::new( ChatCommand::new(
"give_exp", "give_exp",
"{} {}", "{d} {}",
"/give_exp <playername> <amount> : Give experience to specified player", "/give_exp <amount> <playername?> : Give experience to yourself or specify a target player",
true, true,
handle_exp, handle_exp,
), ),
ChatCommand::new(
"set_level",
"{d} {}",
"/set_level <level> <playername?> : Set own Level or specify a target player",
true,
handle_level
),
ChatCommand::new( ChatCommand::new(
"removelights", "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) { fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) { match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) {
(Some(opt_align), Some(id), opt_amount) => { (Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => {
if let Some(alignment) = parse_alignment(entity, &opt_align) { if let Some(alignment) = parse_alignment(entity, &opt_align) {
let amount = opt_amount let amount = opt_amount
.and_then(|a| a.parse().ok()) .and_then(|a| a.parse().ok())
@ -487,7 +494,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
10.0, 10.0,
); );
let body = kind_to_body(id); let body = body();
let new_entity = server let new_entity = server
.state .state
@ -600,17 +607,6 @@ fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option<comp::Alignment>
} }
} }
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) { fn handle_killnpcs(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
let ecs = server.state.ecs(); let ecs = server.state.ecs();
let mut stats = ecs.write_storage::<comp::Stats>(); let mut stats = ecs.write_storage::<comp::Stats>();
@ -1026,27 +1022,74 @@ spawn_rate {:?} "#,
} }
} }
fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { fn find_target(
let (a_alias, a_exp) = scan_fmt_some!(&args, action.arg_fmt, String, i64); ecs: &specs::World,
if let (Some(alias), Some(exp)) = (a_alias, a_exp) { opt_alias: Option<String>,
let ecs = server.state.ecs_mut(); fallback: EcsEntity,
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>()) ) -> Result<EcsEntity, ServerMsg> {
if let Some(alias) = opt_alias {
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join() .join()
.find(|(_, player)| player.alias == alias) .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; let mut error_msg = None;
match opt_player { match target {
Some(_alias) => { Ok(player) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(entity) { if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
stats.exp.change_by(exp); stats.exp.change_by(exp);
} else { } else {
error_msg = Some(ServerMsg::private(String::from("Player has no stats!"))); error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
} }
}, },
_ => { Err(e) => {
error_msg = Some(ServerMsg::private(format!("Player '{}' not found!", alias))); 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::<comp::Stats>().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);
}, },
} }

View File

@ -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<String>,
) {
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<f32>,
body: Body,
light: Option<LightEmitter>,
projectile: Projectile,
gravity: Option<Gravity>,
) {
let state = server.state_mut();
let mut pos = state
.ecs()
.read_storage::<Pos>()
.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<f32>) {
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();
}

View File

@ -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::<Stats>().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::<Player>().get(entity) {
let msg = if let HealthSource::Attack { by } = cause {
state.ecs().entity_from_uid(by.into()).and_then(|attacker| {
state
.ecs()
.read_storage::<Player>()
.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::<Stats>();
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::<Client>()
.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::<comp::Energy>()
.get_mut(entity)
.map(|energy| energy.set_to(energy.maximum(), comp::EnergySource::Revive));
let _ = state
.ecs()
.write_storage::<comp::CharacterState>()
.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<f32>) {
let state = &server.state;
if vel.z <= -37.0 {
if let Some(stats) = state.ecs().write_storage::<comp::Stats>().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::<Client>()
.get_mut(entity)
.is_some()
{
let respawn_point = state
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
state
.ecs()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.revive());
state
.ecs()
.write_storage::<comp::Pos>()
.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<f32>, radius: f32) {
const RAYS: usize = 500;
for _ in 0..RAYS {
let dir = Vec3::new(
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
)
.normalized();
let ecs = server.state.ecs();
let mut block_change = ecs.write_resource::<BlockChange>();
let _ = ecs
.read_resource::<TerrainGrid>()
.ray(pos, pos + dir * radius)
.until(|_| rand::random::<f32>() < 0.05)
.for_each(|pos| block_change.set(pos, Block::empty()))
.cast();
}
}

View File

@ -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::<comp::Mounting>()
.get(mounter)
.is_none()
{
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
.ecs()
.read_storage::<comp::MountState>()
.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::<comp::Mounting>()
.get(mounter)
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
if let Some(mountee_entity) = mountee_entity {
state
.ecs()
.write_storage::<comp::MountState>()
.get_mut(mountee_entity)
.map(|ms| *ms = comp::MountState::Unmounted);
}
state.delete_component::<comp::Mounting>(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::<Client>();
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::<comp::Inventory>();
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::<comp::InventoryUpdate>()
.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::<comp::Player>();
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::<RegionSubscription>();
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::<comp::Agent>().remove(possesse);
// Reset controller of former shell
ecs.write_storage::<comp::Controller>()
.get_mut(possessor)
.map(|c| c.reset());
// Transfer admin powers
{
let mut admins = ecs.write_storage::<comp::Admin>();
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::<comp::Waypoint>();
if let Some(waypoint) = waypoints.remove(possessor) {
waypoints.insert(possesse, waypoint).err().map(|e| {
error!(
"Error inserting waypoint component during possession {:?}",
e
)
});
}
}
}
}
}
}

View File

@ -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::<comp::Item>()
.get_mut(item_entity)
.map(|item| (item.clone(), item_entity))
}),
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Stats>().get_mut(entity)
{
// Insert old item into inventory
if let Some(old_item) = stats.equipment.main.take() {
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Pos>().get(entity)
{
if (
&state.read_storage::<comp::Alignment>(),
&state.read_storage::<comp::Agent>(),
)
.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::<comp::Pos>(),
&state.ecs().read_storage::<comp::Alignment>(),
)
.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::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
}
},
},
_ => {
let _ = state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
},
}
}
state.write_component(entity, comp::InventoryUpdate);
},
comp::InventoryManip::Swap(a, b) => {
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.get_mut(entity)
.and_then(|inv| inv.remove(slot));
if let (Some(item), Some(pos)) =
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
{
dropped_items.push((
*pos,
state
.ecs()
.read_storage::<comp::Ori>()
.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::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 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();
}
}

109
server/src/events/mod.rs Normal file
View File

@ -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<EcsEntity>,
msg: String,
},
}
impl Server {
pub fn handle_events(&mut self) -> Vec<Event> {
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::<EventBus<ServerEvent>>()
.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
}
}

View File

@ -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::<Client>().remove(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().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::<UidAllocator>()
.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::<Uid>().get(entity),
state.read_storage::<comp::Player>().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 }
}

View File

@ -6,6 +6,7 @@ pub mod chunk_generator;
pub mod client; pub mod client;
pub mod cmd; pub mod cmd;
pub mod error; pub mod error;
pub mod events;
pub mod input; pub mod input;
pub mod metrics; pub mod metrics;
pub mod settings; pub mod settings;
@ -13,7 +14,7 @@ pub mod sys;
#[cfg(not(feature = "worldgen"))] mod test_world; #[cfg(not(feature = "worldgen"))] mod test_world;
// Reexports // Reexports
pub use crate::{error::Error, input::Input, settings::ServerSettings}; pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings};
use crate::{ use crate::{
auth_provider::AuthProvider, auth_provider::AuthProvider,
@ -26,19 +27,18 @@ use common::{
assets, comp, assets, comp,
effect::Effect, effect::Effect,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
msg::{ClientMsg, ClientState, PlayerListUpdate, ServerError, ServerInfo, ServerMsg}, msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg},
net::PostOffice, net::PostOffice,
state::{BlockChange, State, TimeOfDay}, state::{State, TimeOfDay},
sync::{Uid, UidAllocator, WorldSyncExt}, sync::{Uid, WorldSyncExt},
terrain::{block::Block, TerrainChunkSize, TerrainGrid}, terrain::TerrainChunkSize,
vol::{ReadVol, RectVolSize, Vox}, vol::{ReadVol, RectVolSize},
}; };
use log::{debug, error, warn}; use log::{debug, error, warn};
use metrics::ServerMetrics; use metrics::ServerMetrics;
use rand::Rng;
use specs::{ use specs::{
join::Join, saveload::MarkerAllocator, world::EntityBuilder as EcsEntityBuilder, Builder, join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow,
Entity as EcsEntity, RunNow, SystemData, WorldExt, SystemData, WorldExt,
}; };
use std::{ use std::{
i32, i32,
@ -57,19 +57,6 @@ use world::{
const CLIENT_TIMEOUT: f64 = 20.0; // Seconds const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
pub enum Event {
ClientConnected {
entity: EcsEntity,
},
ClientDisconnected {
entity: EcsEntity,
},
Chat {
entity: Option<EcsEntity>,
msg: String,
},
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct SpawnPoint(Vec3<f32>); struct SpawnPoint(Vec3<f32>);
@ -318,661 +305,6 @@ impl Server {
} }
/// Handle events coming through via the event bus /// Handle events coming through via the event bus
fn handle_events(&mut self) -> Vec<Event> {
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::<EventBus<ServerEvent>>()
.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::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
)
.normalized();
let ecs = state.ecs();
let mut block_change = ecs.write_resource::<BlockChange>();
let _ = ecs
.read_resource::<TerrainGrid>()
.ray(pos, pos + dir * radius)
.until(|_| rand::random::<f32>() < 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::<comp::Pos>()
.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::<comp::Stats>().get_mut(entity) {
stats.health.change_by(change);
}
}
},
ServerEvent::Destroy { entity, cause } => {
// Chat message
if let Some(player) = state.ecs().read_storage::<comp::Player>().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::<comp::Player>()
.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::<comp::Stats>();
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::<Client>()
.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::<comp::Energy>()
.get_mut(entity)
.map(|energy| {
energy.set_to(energy.maximum(), comp::EnergySource::Revive)
});
let _ = state
.ecs()
.write_storage::<comp::CharacterState>()
.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::<comp::Item>()
.get_mut(item_entity)
.map(|item| (item.clone(), item_entity))
}),
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Stats>()
.get_mut(entity)
{
// Insert old item into inventory
if let Some(old_item) = stats.equipment.main.take() {
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Pos>().get(entity)
{
if (
&state.read_storage::<comp::Alignment>(),
&state.read_storage::<comp::Agent>(),
)
.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::<comp::Pos>(),
&state
.ecs()
.read_storage::<comp::Alignment>(),
)
.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::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
}
},
},
_ => {
let _ = state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
},
}
}
state.write_component(entity, comp::InventoryUpdate);
},
comp::InventoryManip::Swap(a, b) => {
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.get_mut(entity)
.and_then(|inv| inv.remove(slot));
if let (Some(item), Some(pos)) =
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
{
dropped_items.push((
*pos,
state
.ecs()
.read_storage::<comp::Ori>()
.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::<Client>()
.get_mut(entity)
.is_some()
{
let respawn_point = state
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
state
.ecs()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.revive());
state
.ecs()
.write_storage::<comp::Pos>()
.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::<comp::Stats>().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::<comp::Mounting>()
.get(mounter)
.is_none()
{
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
.ecs()
.read_storage::<comp::MountState>()
.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::<comp::Mounting>()
.get(mounter)
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
if let Some(mountee_entity) = mountee_entity {
state
.ecs()
.write_storage::<comp::MountState>()
.get_mut(mountee_entity)
.map(|ms| *ms = comp::MountState::Unmounted);
}
state.delete_component::<comp::Mounting>(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::<Client>();
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::<comp::Inventory>();
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::<comp::InventoryUpdate>()
.insert(possesse, comp::InventoryUpdate);
// Move player component
{
let mut players = ecs.write_storage::<comp::Player>();
if let Some(player) = players.remove(possessor) {
let _ = players.insert(possesse, player);
}
}
// Remove will of the entity
let _ = ecs.write_storage::<comp::Agent>().remove(possesse);
// Transfer admin powers
{
let mut admins = ecs.write_storage::<comp::Admin>();
if let Some(admin) = admins.remove(possessor) {
let _ = admins.insert(possesse, admin);
}
}
// Transfer waypoint
{
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
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::<Client>().remove(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().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::<UidAllocator>()
.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::<Uid>().get(entity),
state.read_storage::<comp::Player>().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::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 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 /// Execute a single server tick, handle input and update the game state by
/// the given duration. /// the given duration.

View File

@ -103,13 +103,14 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos)); server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
} else { } else {
fn get_npc_name< fn get_npc_name<
'a,
Species, Species,
SpeciesData: core::ops::Index<Species, Output = npc::SpeciesNames>, SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>,
>( >(
body_data: &comp::BodyData<npc::BodyNames, SpeciesData>, body_data: &'a comp::BodyData<npc::BodyNames, SpeciesData>,
species: Species, species: Species,
) -> &str { ) -> &'a str {
&body_data.species[species].generic &body_data.species[&species].generic
} }
const SPAWN_NPCS: &'static [fn() -> ( const SPAWN_NPCS: &'static [fn() -> (
String, String,

View File

@ -55,8 +55,7 @@ num = "0.2.0"
backtrace = "0.3.40" backtrace = "0.3.40"
rand = "0.7.2" rand = "0.7.2"
treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" } treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" }
# context for pinning to commit: https://gitlab.com/veloren/veloren/issues/280 rodio = { version = "0.10", default-features = false, features = ["wav", "vorbis"] }
rodio = { git = "https://github.com/RustAudio/rodio", rev = "e5474a2"}
cpal = "0.10" cpal = "0.10"
crossbeam = "=0.7.2" crossbeam = "=0.7.2"
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] } hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
@ -73,6 +72,7 @@ winres = "0.1"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"
git2 = "0.10"
world = { package = "veloren-world", path = "../world" } world = { package = "veloren-world", path = "../world" }
[[bench]] [[bench]]

View File

@ -1,51 +1,125 @@
use crate::audio::fader::Fader; use crate::audio::fader::{FadeDirection, Fader};
use rodio::{Device, Sample, Source, SpatialSink}; use rodio::{Device, Sample, Sink, Source, SpatialSink};
use vek::*; use vek::*;
#[derive(PartialEq, Clone, Copy)]
pub enum AudioType {
Sfx,
Music,
None,
}
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
enum ChannelState { enum ChannelState {
// Init,
// ToPlay,
// Loading,
Playing, Playing,
Stopping, Fading,
Stopped, Stopped,
} }
/// Each MusicChannel has a MusicChannelTag which help us determine how we
/// should transition between music types
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
pub enum ChannelTag { pub enum MusicChannelTag {
TitleMusic, TitleMusic,
Soundtrack, Exploration,
} }
pub struct Channel { /// A MusicChannel uses a non-positional audio sink designed to play music which
id: usize, /// is always heard at the player's position
sink: SpatialSink, pub struct MusicChannel {
audio_type: AudioType, tag: MusicChannelTag,
sink: Sink,
state: ChannelState, state: ChannelState,
fader: Fader, fader: Fader,
tag: Option<ChannelTag>, }
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<S>(&mut self, source: S, tag: MusicChannelTag)
where
S: Source + Send + 'static,
S::Item: Sample,
S::Item: Send,
<S as std::iter::Iterator>::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<f32>, pub pos: Vec3<f32>,
} }
// TODO: Implement asynchronous loading impl SfxChannel {
impl Channel {
/// Create an empty channel for future use
pub fn new(device: &Device) -> Self { pub fn new(device: &Device) -> Self {
Self { Self {
id: 0,
sink: SpatialSink::new(device, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.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(), pos: Vec3::zero(),
} }
} }
@ -57,31 +131,13 @@ impl Channel {
S::Item: Send, S::Item: Send,
<S as std::iter::Iterator>::Item: std::fmt::Debug, <S as std::iter::Iterator>::Item: std::fmt::Debug,
{ {
self.state = ChannelState::Playing;
self.sink.append(source); 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<ChannelTag>) { self.tag = tag; }
pub fn get_tag(&self) -> Option<ChannelTag> { 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 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_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); } 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]) { pub fn set_right_ear_position(&mut self, pos: [f32; 3]) {
self.sink.set_right_ear_position(pos); 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 => {},
}
}
} }

View File

@ -6,13 +6,18 @@ pub struct Fader {
volume_to: f32, volume_to: f32,
is_running: bool, 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 } fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b }
impl Fader { 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 { Self {
length: time, length,
running_time: 0.0, running_time: 0.0,
volume_from, volume_from,
volume_to, volume_to,
@ -20,23 +25,28 @@ impl Fader {
} }
} }
pub fn fade_in(time: f32) -> Self { pub fn fade_in(time: f32, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
Self {
length: time, pub fn fade_out(time: f32, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
running_time: 0.0,
volume_from: 0.0, pub fn update_target_volume(&mut self, volume: f32) {
volume_to: 1.0, match self.direction() {
is_running: true, 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 { pub fn direction(&self) -> FadeDirection {
Self { if self.volume_to < self.volume_from {
length: time, FadeDirection::Out
running_time: 0.0, } else {
volume_from, FadeDirection::In
volume_to: 0.0,
is_running: true,
} }
} }
@ -60,3 +70,107 @@ impl Fader {
pub fn is_finished(&self) -> bool { self.running_time >= self.length || !self.is_running } 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);
}
}

View File

@ -4,13 +4,13 @@ pub mod music;
pub mod sfx; pub mod sfx;
pub mod soundcache; pub mod soundcache;
use channel::{AudioType, Channel, ChannelTag}; use channel::{MusicChannel, MusicChannelTag, SfxChannel};
use fader::Fader; use fader::Fader;
use soundcache::SoundCache; use soundcache::SoundCache;
use common::assets; use common::assets;
use cpal::traits::DeviceTrait; use cpal::traits::DeviceTrait;
use rodio::{Decoder, Device}; use rodio::{source::Source, Decoder, Device};
use vek::*; use vek::*;
const FALLOFF: f32 = 0.13; const FALLOFF: f32 = 0.13;
@ -21,8 +21,8 @@ pub struct AudioFrontend {
audio_device: Option<Device>, audio_device: Option<Device>,
sound_cache: SoundCache, sound_cache: SoundCache,
channels: Vec<Channel>, music_channels: Vec<MusicChannel>,
next_channel_id: usize, sfx_channels: Vec<SfxChannel>,
sfx_volume: f32, sfx_volume: f32,
music_volume: f32, music_volume: f32,
@ -36,21 +36,23 @@ pub struct AudioFrontend {
impl AudioFrontend { impl AudioFrontend {
/// Construct with given device /// Construct with given device
pub fn new(device: String, channel_num: usize) -> Self { pub fn new(device: String, max_sfx_channels: usize) -> Self {
let mut channels = Vec::with_capacity(channel_num); let mut sfx_channels = Vec::with_capacity(max_sfx_channels);
let audio_device = get_device_raw(&device); let audio_device = get_device_raw(&device);
if let Some(audio_device) = &audio_device { if let Some(audio_device) = &audio_device {
for _i in 0..channel_num { for _ in 0..max_sfx_channels {
channels.push(Channel::new(&audio_device)); sfx_channels.push(SfxChannel::new(&audio_device));
} }
} }
Self { Self {
device: device.clone(), device: device.clone(),
device_list: list_devices(), device_list: list_devices(),
audio_device, audio_device,
sound_cache: SoundCache::new(), sound_cache: SoundCache::new(),
channels, music_channels: Vec::new(),
next_channel_id: 1, sfx_channels,
sfx_volume: 1.0, sfx_volume: 1.0,
music_volume: 1.0, music_volume: 1.0,
listener_pos: Vec3::zero(), listener_pos: Vec3::zero(),
@ -67,8 +69,8 @@ impl AudioFrontend {
device_list: Vec::new(), device_list: Vec::new(),
audio_device: None, audio_device: None,
sound_cache: SoundCache::new(), sound_cache: SoundCache::new(),
channels: Vec::new(), music_channels: Vec::new(),
next_channel_id: 1, sfx_channels: Vec::new(),
sfx_volume: 1.0, sfx_volume: 1.0,
music_volume: 1.0, music_volume: 1.0,
listener_pos: Vec3::zero(), 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) { pub fn maintain(&mut self, dt: f32) {
for channel in self.channels.iter_mut() { self.music_channels.retain(|c| !c.is_done());
channel.update(dt);
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, &mut self,
audio_type: AudioType, next_channel_tag: MusicChannelTag,
channel_tag: Option<ChannelTag>, ) -> Option<&mut MusicChannel> {
) -> Option<&mut Channel> { if let Some(audio_device) = &self.audio_device {
if let Some(channel) = self.channels.iter_mut().find(|c| c.is_done()) { if self.music_channels.is_empty() {
let id = self.next_channel_id; let mut next_music_channel = MusicChannel::new(&audio_device);
self.next_channel_id += 1; next_music_channel.set_volume(self.music_volume);
let volume = match audio_type { self.music_channels.push(next_music_channel);
AudioType::Music => self.music_volume, } else {
_ => self.sfx_volume, let existing_channel = self.music_channels.last_mut()?;
};
channel.set_id(id); if existing_channel.get_tag() != next_channel_tag {
channel.set_tag(channel_tag); // Fade the existing channel out. It will be removed when the fade completes.
channel.set_audio_type(audio_type); existing_channel.set_fader(Fader::fade_out(2.0, self.music_volume));
channel.set_volume(volume);
Some(channel) let mut next_music_channel = MusicChannel::new(&audio_device);
} else {
None 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_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
pub fn play_sound(&mut self, sound: &str, pos: Vec3<f32>) -> Option<usize> {
if self.audio_device.is_some() { if self.audio_device.is_some() {
let calc_pos = ((pos - self.listener_pos) * FALLOFF).into_array(); 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 left_ear = self.listener_ear_left.into_array();
let right_ear = self.listener_ear_right.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_emitter_position(calc_pos);
channel.set_left_ear_position(left_ear); channel.set_left_ear_position(left_ear);
channel.set_right_ear_position(right_ear); channel.set_right_ear_position(right_ear);
channel.play(sound); channel.play(sound);
return Some(channel.get_id());
} }
} }
None
} }
pub fn play_music(&mut self, sound: &str, channel_tag: Option<ChannelTag>) -> Option<usize> { fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
if self.audio_device.is_some() { if let Some(channel) = self.get_music_channel(channel_tag) {
if let Some(channel) = self.get_channel(AudioType::Music, channel_tag) { let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); let sound = Decoder::new(file).expect("Failed to decode sound");
let sound = Decoder::new(file).expect("Failed to decode sound");
channel.set_emitter_position([0.0; 3]); channel.play(sound, channel_tag);
channel.play(sound);
return Some(channel.get_id());
}
} }
None
} }
pub fn set_listener_pos(&mut self, pos: &Vec3<f32>, ori: &Vec3<f32>) { pub fn set_listener_pos(&mut self, pos: &Vec3<f32>, ori: &Vec3<f32>) {
@ -161,8 +177,8 @@ impl AudioFrontend {
self.listener_ear_left = pos_left; self.listener_ear_left = pos_left;
self.listener_ear_right = pos_right; self.listener_ear_right = pos_right;
for channel in self.channels.iter_mut() { for channel in self.sfx_channels.iter_mut() {
if !channel.is_done() && channel.get_audio_type() == AudioType::Sfx { if !channel.is_done() {
// TODO: Update this to correctly determine the updated relative position of // TODO: Update this to correctly determine the updated relative position of
// the SFX emitter when the player (listener) moves // the SFX emitter when the player (listener) moves
// channel.set_emitter_position( // channel.set_emitter_position(
@ -174,32 +190,18 @@ impl AudioFrontend {
} }
} }
pub fn play_title_music(&mut self) -> Option<usize> { pub fn play_title_music(&mut self) {
if self.music_enabled() { if self.music_enabled() {
self.play_music( self.play_music(
"voxygen.audio.soundtrack.veloren_title_tune", "voxygen.audio.soundtrack.veloren_title_tune",
Some(ChannelTag::TitleMusic), MusicChannelTag::TitleMusic,
) )
} else {
None
} }
} }
pub fn stop_title_music(&mut self) { pub fn play_exploration_music(&mut self, item: &str) {
let index = self.channels.iter().position(|c| { if self.music_enabled() {
!c.is_done() && c.get_tag().is_some() && c.get_tag().unwrap() == ChannelTag::TitleMusic self.play_music(item, MusicChannelTag::Exploration)
});
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);
} }
} }
@ -214,24 +216,16 @@ impl AudioFrontend {
pub fn set_sfx_volume(&mut self, sfx_volume: f32) { pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
self.sfx_volume = sfx_volume; self.sfx_volume = sfx_volume;
for channel in self.channels.iter_mut() { for channel in self.sfx_channels.iter_mut() {
if channel.get_audio_type() == AudioType::Sfx { channel.set_volume(sfx_volume);
channel.set_volume(sfx_volume);
}
} }
} }
pub fn set_music_volume(&mut self, music_volume: f32) { pub fn set_music_volume(&mut self, music_volume: f32) {
self.music_volume = music_volume; self.music_volume = music_volume;
for channel in self.channels.iter_mut() { for channel in self.music_channels.iter_mut() {
if channel.get_audio_type() == AudioType::Music { channel.set_volume(music_volume);
if music_volume > 0.0 {
channel.set_volume(music_volume);
} else {
channel.stop(Fader::fade_out(0.0, 0.0));
}
}
} }
} }

View File

@ -1,4 +1,4 @@
use crate::audio::{channel::ChannelTag, AudioFrontend}; use crate::audio::AudioFrontend;
use client::Client; use client::Client;
use common::assets; use common::assets;
use rand::{seq::IteratorRandom, thread_rng}; use rand::{seq::IteratorRandom, thread_rng};
@ -31,7 +31,6 @@ pub struct MusicMgr {
soundtrack: SoundtrackCollection, soundtrack: SoundtrackCollection,
began_playing: Instant, began_playing: Instant,
next_track_change: f64, next_track_change: f64,
current_music: Option<usize>,
last_track: String, last_track: String,
} }
@ -41,7 +40,6 @@ impl MusicMgr {
soundtrack: Self::load_soundtrack_items(), soundtrack: Self::load_soundtrack_items(),
began_playing: Instant::now(), began_playing: Instant::now(),
next_track_change: 0.0, next_track_change: 0.0,
current_music: None,
last_track: String::from("None"), last_track: String::from("None"),
} }
} }
@ -50,15 +48,15 @@ impl MusicMgr {
if audio.music_enabled() if audio.music_enabled()
&& self.began_playing.elapsed().as_secs_f64() > self.next_track_change && 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<usize> { fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) {
const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0; const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0;
let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32; 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 mut rng = thread_rng();
let track = self let track = self
@ -79,10 +77,10 @@ impl MusicMgr {
self.began_playing = Instant::now(); self.began_playing = Instant::now();
self.next_track_change = track.length + SILENCE_BETWEEN_TRACKS_SECONDS; 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 { if game_time > DAY_START_SECONDS && game_time < DAY_END_SECONDS {
DayPeriod::Day DayPeriod::Day
} else { } else {

View File

@ -5,7 +5,7 @@ use crate::audio::sfx::{SfxTriggerItem, SfxTriggers};
use client::Client; use client::Client;
use common::{ use common::{
comp::{Body, CharacterState, Pos, Vel}, comp::{Body, CharacterState, Item, ItemKind, Pos, Stats, ToolData, Vel},
event::{EventBus, SfxEvent, SfxEventItem}, event::{EventBus, SfxEvent, SfxEventItem},
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
@ -16,6 +16,7 @@ use vek::*;
#[derive(Clone)] #[derive(Clone)]
struct LastSfxEvent { struct LastSfxEvent {
event: SfxEvent, event: SfxEvent,
weapon_drawn: bool,
time: Instant, time: Instant,
} }
@ -31,7 +32,7 @@ impl MovementEventMapper {
} }
pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { 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 ecs = client.state().ecs();
let player_position = ecs let player_position = ecs
@ -39,11 +40,12 @@ impl MovementEventMapper {
.get(client.entity()) .get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .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.entities(),
&ecs.read_storage::<Pos>(), &ecs.read_storage::<Pos>(),
&ecs.read_storage::<Vel>(), &ecs.read_storage::<Vel>(),
&ecs.read_storage::<Body>(), &ecs.read_storage::<Body>(),
&ecs.read_storage::<Stats>(),
ecs.read_storage::<CharacterState>().maybe(), ecs.read_storage::<CharacterState>().maybe(),
) )
.join() .join()
@ -57,27 +59,36 @@ impl MovementEventMapper {
.entry(entity) .entry(entity)
.or_insert_with(|| LastSfxEvent { .or_insert_with(|| LastSfxEvent {
event: SfxEvent::Idle, event: SfxEvent::Idle,
weapon_drawn: false,
time: Instant::now(), time: Instant::now(),
}); });
let mapped_event = match body { let mapped_event = match body {
Body::Humanoid(_) => Self::map_movement_event(character, state.event.clone()), Body::Humanoid(_) => Self::map_movement_event(character, state, vel.0, stats),
Body::QuadrupedMedium(_) => { Body::QuadrupedMedium(_)
// TODO: Quadriped running sfx | Body::QuadrupedSmall(_)
SfxEvent::Idle | 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 // Check for SFX config entry for this movement
if Self::should_emit(state, triggers.get_key_value(&mapped_event)) { if Self::should_emit(state, triggers.get_key_value(&mapped_event)) {
ecs.read_resource::<EventBus<SfxEventItem>>() ecs.read_resource::<EventBus<SfxEventItem>>()
.emitter() .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 // Update the last play time
state.event = mapped_event; state.event = mapped_event;
state.time = Instant::now(); state.time = Instant::now();
state.weapon_drawn = character.is_wielded();
} else { } else {
// Keep the last event, it may not have an SFX trigger but it helps us determine // Keep the last event, it may not have an SFX trigger but it helps us determine
// the next one // the next one
@ -95,7 +106,7 @@ impl MovementEventMapper {
/// they have not triggered an event for > n seconds. This prevents /// they have not triggered an event for > n seconds. This prevents
/// stale records from bloating the Map size. /// stale records from bloating the Map size.
fn cleanup(&mut self, player: EcsEntity) { fn cleanup(&mut self, player: EcsEntity) {
const TRACKING_TIMEOUT: u64 = 15; const TRACKING_TIMEOUT: u64 = 10;
let now = Instant::now(); let now = Instant::now();
self.event_history.retain(|entity, event| { self.event_history.retain(|entity, event| {
@ -129,8 +140,33 @@ impl MovementEventMapper {
/// as opening or closing the glider. These methods translate those /// as opening or closing the glider. These methods translate those
/// entity states with some additional data into more specific /// entity states with some additional data into more specific
/// `SfxEvent`'s which we attach sounds to /// `SfxEvent`'s which we attach sounds to
fn map_movement_event(current_event: &CharacterState, previous_event: SfxEvent) -> SfxEvent { fn map_movement_event(
match (previous_event, current_event) { current_event: &CharacterState,
previous_event: &LastSfxEvent,
vel: Vec3<f32>,
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::Roll(_)) => SfxEvent::Roll,
(_, CharacterState::Climb(_)) => SfxEvent::Climb, (_, CharacterState::Climb(_)) => SfxEvent::Climb,
(SfxEvent::Glide, CharacterState::Idle(_)) => SfxEvent::GliderClose, (SfxEvent::Glide, CharacterState::Idle(_)) => SfxEvent::GliderClose,
@ -144,135 +180,33 @@ impl MovementEventMapper {
_ => SfxEvent::Idle, _ => SfxEvent::Idle,
} }
} }
}
#[cfg(test)] /// Maps a limited set of movements for other non-humanoid entities
mod tests { fn map_non_humanoid_movement_event(current_event: &CharacterState, vel: Vec3<f32>) -> SfxEvent {
use super::*; if let CharacterState::Idle(_) = current_event {
use common::{comp::CharacterState, event::SfxEvent}; if vel.magnitude() > 0.1 {
use std::time::{Duration, Instant}; SfxEvent::Run
} else {
#[test] SfxEvent::Idle
fn no_item_config_no_emit() { }
let last_sfx_event = LastSfxEvent { } else {
event: SfxEvent::Idle, SfxEvent::Idle
time: Instant::now(), }
};
let result = MovementEventMapper::should_emit(&last_sfx_event, None);
assert_eq!(result, false);
} }
#[test] /// Returns a relative volume value for a body type. This helps us emit sfx
fn config_but_played_since_threshold_no_emit() { /// at a volume appropriate fot the entity we are emitting the event for
let event = SfxEvent::Run; fn get_volume_for_body_type(body: &Body) -> f32 {
match body {
let trigger_item = SfxTriggerItem { Body::Humanoid(_) => 0.9,
files: vec![String::from("some.path.to.sfx.file")], Body::QuadrupedSmall(_) => 0.3,
threshold: 1.0, Body::QuadrupedMedium(_) => 0.7,
}; Body::BirdMedium(_) => 0.3,
Body::BirdSmall(_) => 0.2,
// Triggered a 'Run' 0 seconds ago Body::BipedLarge(_) => 1.0,
let last_sfx_event = LastSfxEvent { _ => 0.9,
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);
} }
} }
#[cfg(test)] mod tests;

View File

@ -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);
}

View File

@ -91,7 +91,7 @@ impl SfxMgr {
}, },
}; };
audio.play_sound(sfx_file, position); audio.play_sfx(sfx_file, position, event.vol);
} }
} }
} }

View File

@ -1,9 +1,9 @@
use super::{ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::{ItemImgs, ItemKey}, 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 client::Client;
use conrod_core::{ use conrod_core::{
color, image, color, image,
@ -35,7 +35,7 @@ pub struct Bag<'a> {
client: &'a Client, client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
item_imgs: &'a ItemImgs, item_imgs: &'a ItemImgs,
fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
@ -48,7 +48,7 @@ impl<'a> Bag<'a> {
client: &'a Client, client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
item_imgs: &'a ItemImgs, item_imgs: &'a ItemImgs,
fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
pulse: f32, pulse: f32,
@ -118,10 +118,11 @@ impl<'a> Widget for Bag<'a> {
5.0, 5.0,
) )
}) })
.title_font_size(15) .title_font_size(self.fonts.cyri.scale(15))
.parent(ui.window) .parent(ui.window)
.desc_font_size(12) .desc_font_size(self.fonts.cyri.scale(12))
.title_text_color(TEXT_COLOR) .title_text_color(TEXT_COLOR)
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR); .desc_text_color(TEXT_COLOR);
// Bag parts // Bag parts

View File

@ -3,8 +3,8 @@ use conrod_core::{
widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use super::{img_ids::Imgs, Fonts, Windows, TEXT_COLOR}; use super::{img_ids::Imgs, Windows, TEXT_COLOR};
use crate::ui::ToggleButton; use crate::ui::{fonts::ConrodVoxygenFonts, ToggleButton};
widget_ids! { widget_ids! {
struct Ids { struct Ids {
@ -31,7 +31,7 @@ pub struct Buttons<'a> {
show_bag: bool, show_bag: bool,
imgs: &'a Imgs, imgs: &'a Imgs,
_fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
} }
@ -42,14 +42,14 @@ impl<'a> Buttons<'a> {
show_map: bool, show_map: bool,
show_bag: bool, show_bag: bool,
imgs: &'a Imgs, imgs: &'a Imgs,
_fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
) -> Self { ) -> Self {
Self { Self {
open_windows, open_windows,
show_map, show_map,
show_bag, show_bag,
imgs, imgs,
_fonts, fonts,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
} }
} }
@ -101,6 +101,7 @@ impl<'a> Widget for Buttons<'a> {
Text::new("B") Text::new("B")
.bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.bag_text, ui); .set(state.ids.bag_text, ui);
} else { } else {
@ -111,6 +112,7 @@ impl<'a> Widget for Buttons<'a> {
Text::new("B") Text::new("B")
.bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0) .bottom_right_with_margins_on(state.ids.bag, 0.0, 0.0)
.font_size(10) .font_size(10)
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.bag_text, ui); .set(state.ids.bag_text, ui);
} }
@ -122,6 +124,7 @@ impl<'a> Widget for Buttons<'a> {
.hover_image(self.imgs.settings_hover) .hover_image(self.imgs.settings_hover)
.press_image(self.imgs.settings_press) .press_image(self.imgs.settings_press)
.label("N") .label("N")
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(10) .label_font_size(10)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(-7.0)) .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) .hover_image(self.imgs.map_hover)
.press_image(self.imgs.map_press) .press_image(self.imgs.map_press)
.label("M") .label("M")
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(10) .label_font_size(10)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(-7.0)) .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) .hover_image(self.imgs.social_hover)
.press_image(self.imgs.social_press) .press_image(self.imgs.social_press)
.label("O") .label("O")
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(10) .label_font_size(10)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(-7.0)) .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) .hover_image(self.imgs.spellbook_hover)
.press_image(self.imgs.spellbook_press) .press_image(self.imgs.spellbook_press)
.label("P") .label("P")
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(10) .label_font_size(10)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(-7.0)) .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) .hover_image(self.imgs.character_hover)
.press_image(self.imgs.character_press) .press_image(self.imgs.character_press)
.label("C") .label("C")
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(10) .label_font_size(10)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(-7.0)) .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) .hover_image(self.imgs.qlog_hover)
.press_image(self.imgs.qlog_press) .press_image(self.imgs.qlog_press)
.label("L") .label("L")
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(10) .label_font_size(10)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(-7.0)) .label_y(conrod_core::position::Relative::Scalar(-7.0))

View File

@ -1,5 +1,5 @@
use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR, XP_COLOR}; use super::{img_ids::Imgs, Show, TEXT_COLOR, XP_COLOR};
use crate::i18n::VoxygenLocalization; use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts};
use common::comp::Stats; use common::comp::Stats;
use conrod_core::{ use conrod_core::{
color, color,
@ -71,7 +71,7 @@ widget_ids! {
pub struct CharacterWindow<'a> { pub struct CharacterWindow<'a> {
_show: &'a Show, _show: &'a Show,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
stats: &'a Stats, stats: &'a Stats,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
@ -84,7 +84,7 @@ impl<'a> CharacterWindow<'a> {
_show: &'a Show, _show: &'a Show,
stats: &'a Stats, stats: &'a Stats,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
) -> Self { ) -> Self {
Self { Self {
@ -155,8 +155,8 @@ impl<'a> Widget for CharacterWindow<'a> {
.get("character_window.character_name"), .get("character_window.character_name"),
) )
.mid_top_with_margin_on(state.charwindow_frame, 6.0) .mid_top_with_margin_on(state.charwindow_frame, 6.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri.conrod_id)
.font_size(14) .font_size(self.fonts.cyri.scale(14))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_title, ui); .set(state.charwindow_title, ui);
@ -352,8 +352,8 @@ impl<'a> Widget for CharacterWindow<'a> {
// Level // Level
Text::new(&level) Text::new(&level)
.mid_top_with_margin_on(state.charwindow_rectangle, 10.0) .mid_top_with_margin_on(state.charwindow_rectangle, 10.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri.conrod_id)
.font_size(30) .font_size(self.fonts.cyri.scale(30))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_tab1_level, ui); .set(state.charwindow_tab1_level, ui);
@ -376,8 +376,8 @@ impl<'a> Widget for CharacterWindow<'a> {
// Exp-Text // Exp-Text
Text::new(&exp_treshold) Text::new(&exp_treshold)
.mid_top_with_margin_on(state.charwindow_tab1_expbar, 10.0) .mid_top_with_margin_on(state.charwindow_tab1_expbar, 10.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri.conrod_id)
.font_size(15) .font_size(self.fonts.cyri.scale(15))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_tab1_exp, ui); .set(state.charwindow_tab1_exp, ui);
@ -395,8 +395,8 @@ impl<'a> Widget for CharacterWindow<'a> {
.get("character_window.character_stats"), .get("character_window.character_stats"),
) )
.top_left_with_margins_on(state.charwindow_rectangle, 140.0, 5.0) .top_left_with_margins_on(state.charwindow_rectangle, 140.0, 5.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri.conrod_id)
.font_size(16) .font_size(self.fonts.cyri.scale(16))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_tab1_statnames, ui); .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 self.stats.endurance, self.stats.fitness, self.stats.willpower
)) ))
.top_right_with_margins_on(state.charwindow_rectangle, 140.0, 5.0) .top_right_with_margins_on(state.charwindow_rectangle, 140.0, 5.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri.conrod_id)
.font_size(16) .font_size(self.fonts.cyri.scale(16))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_tab1_stats, ui); .set(state.charwindow_tab1_stats, ui);

View File

@ -1,8 +1,8 @@
use super::{ use super::{
img_ids::Imgs, Fonts, BROADCAST_COLOR, FACTION_COLOR, GAME_UPDATE_COLOR, GROUP_COLOR, img_ids::Imgs, BROADCAST_COLOR, FACTION_COLOR, GAME_UPDATE_COLOR, GROUP_COLOR, KILL_COLOR,
KILL_COLOR, META_COLOR, PRIVATE_COLOR, SAY_COLOR, TELL_COLOR, TEXT_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 client::Event as ClientEvent;
use common::{msg::validate_chat_msg, ChatType}; use common::{msg::validate_chat_msg, ChatType};
use conrod_core::{ use conrod_core::{
@ -34,7 +34,7 @@ pub struct Chat<'a> {
global_state: &'a GlobalState, global_state: &'a GlobalState,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
@ -48,7 +48,7 @@ impl<'a> Chat<'a> {
new_messages: &'a mut VecDeque<ClientEvent>, new_messages: &'a mut VecDeque<ClientEvent>,
global_state: &'a GlobalState, global_state: &'a GlobalState,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a ConrodVoxygenFonts,
) -> Self { ) -> Self {
Self { Self {
new_messages, new_messages,
@ -187,8 +187,8 @@ impl<'a> Widget for Chat<'a> {
.restrict_to_height(false) .restrict_to_height(false)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.line_spacing(2.0) .line_spacing(2.0)
.font_size(15) .font_size(self.fonts.opensans.scale(15))
.font_id(self.fonts.opensans); .font_id(self.fonts.opensans.conrod_id);
if let Some(pos) = self.force_cursor { if let Some(pos) = self.force_cursor {
text_edit = text_edit.cursor_pos(pos); text_edit = text_edit.cursor_pos(pos);
@ -251,8 +251,8 @@ impl<'a> Widget for Chat<'a> {
ChatType::Kill => KILL_COLOR, ChatType::Kill => KILL_COLOR,
}; };
let text = Text::new(&message) let text = Text::new(&message)
.font_size(15) .font_size(self.fonts.opensans.scale(15))
.font_id(self.fonts.opensans) .font_id(self.fonts.opensans.conrod_id)
.w(470.0) .w(470.0)
.color(color) .color(color)
.line_spacing(2.0); .line_spacing(2.0);
@ -270,8 +270,8 @@ impl<'a> Widget for Chat<'a> {
// Needs to be larger than the space above. // Needs to be larger than the space above.
Some( Some(
Text::new("") Text::new("")
.font_size(6) .font_size(self.fonts.opensans.scale(6))
.font_id(self.fonts.opensans) .font_id(self.fonts.opensans.conrod_id)
.w(470.0), .w(470.0),
) )
}; };

Some files were not shown because too many files have changed in this diff Show More