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

This commit is contained in:
timokoesters 2020-02-24 21:34:17 +01:00
commit d0439fdd84
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:
- veloren-docker
script:
- cargo tarpaulin -v
allow_failure: true
- echo "Workaround, tarpaulin fails due some rust files are already deleted, so we just stack tarpaulin. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail"
- cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v
benchmarks:
stage: post
@ -121,7 +121,18 @@ benchmarks:
script:
- unset DISABLE_GIT_LFS_CHECK
- cargo bench
localization-status:
variables:
GIT_DEPTH: 0
stage: post
when: delayed
start_in: 5 seconds
allow_failure: true
tags:
- veloren-docker
script:
- cargo test -q test_all_localizations -- --nocapture --ignored
linux:
stage: post
@ -170,5 +181,4 @@ windows:
- assets/
- LICENSE
expire_in: 1 week
# --

View File

@ -10,9 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added music system
- Added zoomable and rotatable minimap
- Added rotating orientation marker to main-map
- Added daily Mac builds
- Allow spawning individual pet species, not just generic body kinds.
- Configurable fonts
- Tanslation status tracking
- Added gamma setting
- Added new orc hairstyles
- Added sfx for wielding/unwielding weapons
### Changed
- Brighter / higher contrast main-map
- Removed highlighting of non-collectible sprites
- Fixed /give_exp ignoring player argument
- Extend run sfx to small animals to prevent sneak attacks by geese.
### Removed
## [0.5.0] - 2019-01-31

177
Cargo.lock generated
View File

@ -139,6 +139,11 @@ name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.40"
@ -324,6 +329,10 @@ dependencies = [
name = "cc"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cexpr"
@ -391,11 +400,6 @@ dependencies = [
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "claxon"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clipboard-win"
version = "2.2.0"
@ -1143,6 +1147,20 @@ dependencies = [
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "git2"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gl_generator"
version = "0.11.0"
@ -1399,6 +1417,16 @@ dependencies = [
"unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "idna"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "image"
version = "0.22.3"
@ -1475,6 +1503,14 @@ name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "jobserver"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "jpeg-decoder"
version = "0.1.16"
@ -1523,6 +1559,19 @@ name = "libc"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libgit2-sys"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libssh2-sys 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libloading"
version = "0.5.2"
@ -1532,6 +1581,30 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libssh2-sys"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libz-sys"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "line_drawing"
version = "0.7.0"
@ -1583,14 +1656,6 @@ name = "lzw"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "mach"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@ -1666,23 +1731,6 @@ dependencies = [
"x11-dl 2.18.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "minimp3"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"minimp3-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "minimp3-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mio"
version = "0.6.21"
@ -1961,6 +2009,23 @@ dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "openssl-sys"
version = "0.9.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "orbclient"
version = "0.3.27"
@ -2555,15 +2620,13 @@ dependencies = [
[[package]]
name = "rodio"
version = "0.9.0"
source = "git+https://github.com/RustAudio/rodio?rev=e5474a2#e5474a2ef15f2d0a3bae2538de159b6d3e5bdf79"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cpal 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hound 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
"minimp3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2816,16 +2879,6 @@ name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "slice-deque"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smallvec"
version = "0.6.13"
@ -3154,6 +3207,16 @@ dependencies = [
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "uvth"
version = "3.1.1"
@ -3164,6 +3227,11 @@ dependencies = [
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vek"
version = "0.9.11"
@ -3291,6 +3359,7 @@ dependencies = [
"gfx 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_device_gl 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_window_glutin 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"glsl-include 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"glutin 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
"guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere)",
@ -3300,7 +3369,7 @@ dependencies = [
"msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)",
"rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3624,6 +3693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum atom 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3c86699c3f02778ec07158376991c8f783dd1f2f95c579ffaf0738dc984b2fe2"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
@ -3654,7 +3724,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
"checksum clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f86c952727a495bda7abaf09bafdee1a939194dd793d9a8e26281df55ac43b00"
"checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54"
@ -3733,6 +3802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af"
"checksum gio 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2db9fad8f1b0d4c7338a210a6cbdf081dcc1a3c223718c698c4f313f6c288acb"
"checksum gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a57872499171d279f8577ce83837da4cae62b08dd32892236ed67ab7ea61030"
"checksum git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c1af51ea8a906616af45a4ce78eacf25860f7a13ae7bf8a814693f0f4037a26"
"checksum gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39a23d5e872a275135d66895d954269cf5e8661d234eb1c2480f4ce0d586acbd"
"checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a"
"checksum gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5"
@ -3758,6 +3828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4be8aaefbe7545dc42ae925afb55a0098f226a3fe5ef721872806f44f57826"
"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2"
"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
@ -3767,6 +3838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
"checksum jpeg-decoder 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "c1aae18ffeeae409c6622c3b6a7ee49792a7e5a062eea1b135fbb74e301792ba"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
@ -3774,7 +3846,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
"checksum lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
"checksum libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4870c781f6063efb83150cd22c1ddf6ecf58531419e7570cdcced46970f64a16"
"checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753"
"checksum libssh2-sys 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "36aa6e813339d3a063292b77091dfbbb6152ff9006a459895fa5bebed7d34f10"
"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
"checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc"
@ -3782,7 +3857,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum lz4-compress 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f966533a922a9bba9e95e594c1fdb3b9bf5fdcdb11e37e51ad84cd76e468b91"
"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
@ -3792,8 +3866,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
"checksum mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0d977de9ee851a0b16e932979515c0f3da82403183879811bc97d50bd9cc50f7"
"checksum minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)" = "<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-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
@ -3821,6 +3893,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
"checksum objc_id 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
"checksum ogg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d79f1db9148be9d0e174bb3ac890f6030fcb1ed947267c5a91ee4c91b5a91e15"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986"
"checksum orbclient 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f8b18f57ab94fbd058e30aa57f712ec423c0bb7403f8493a6c58eef0c36d9402"
"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518"
"checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
@ -3888,7 +3962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)" = "<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 roots 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e4c67c712ab62be58b24ab8960e2b95dd4ee00aac115c76f2709657821fe376d"
"checksum rouille 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "112568052ec17fa26c6c11c40acbb30d3ad244bf3d6da0be181f5e7e42e5004f"
@ -3919,7 +3993,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5752e017e03af9d735b4b069f53b7a7fd90fefafa04d8bd0c25581b0bff437f"
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7"
"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86"
"checksum smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa"
@ -3959,7 +4032,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
"checksum uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e59a167890d173eb0fcd7a1b99b84dc05c521ae8d76599130b8e19bef287abbf"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
"checksum vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "1eb3ca8ea588deba055424cc1a79a830428b2f6c270b8d8f91946f660fa4d8ee"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

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

View File

@ -23,5 +23,17 @@
],
threshold: 0.5,
),
Wield(Sword): (
files: [
"voxygen.audio.sfx.weapon.sword_out",
],
threshold: 0.5,
),
Unwield(Sword): (
files: [
"voxygen.audio.sfx.weapon.sword_in",
],
threshold: 0.5,
),
}
)

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",
),
convert_utf8_to_ascii: false,
fonts: {
"opensans": Font (
asset_key: "voxygen.font.OpenSans-Regular",
scale_ratio: 1.0,
),
"metamorph": Font (
asset_key: "voxygen.font.Metamorphous-Regular",
scale_ratio: 1.0,
),
"alkhemi": Font (
asset_key: "voxygen.font.Alkhemikal",
scale_ratio: 1.0,
),
"wizard": Font (
asset_key: "voxygen.font.wizard",
scale_ratio: 1.0,
),
"cyri": Font (
asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended",
scale_ratio: 1.0,
),
},
string_map: {
/// Start Common section
// Texts used in multiple locations with the same formatting
@ -186,6 +208,7 @@ Enjoy your stay in the World of Veloren."#,
"hud.settings.view_distance": "View Distance",
"hud.settings.maximum_fps": "Maximum FPS",
"hud.settings.fov": "Field of View (deg)",
"hud.settings.gamma": "Gamma",
"hud.settings.antialiasing_mode": "AntiAliasing Mode",
"hud.settings.cloud_rendering_mode": "Cloud Rendering Mode",
"hud.settings.fluid_rendering_mode": "Fluid Rendering Mode",

View File

@ -4,7 +4,29 @@ VoxygenLocalization(
language_name: "Français",
language_identifier: "fr_FR",
),
convert_utf8_to_ascii: true,
convert_utf8_to_ascii: false,
fonts: {
"opensans": Font (
asset_key: "voxygen.font.OpenSans-Regular",
scale_ratio: 1.0,
),
"metamorph": Font (
asset_key: "voxygen.font.Metamorphous-Regular",
scale_ratio: 1.0,
),
"alkhemi": Font (
asset_key: "voxygen.font.Alkhemikal",
scale_ratio: 1.0,
),
"wizard": Font (
asset_key: "voxygen.font.wizard",
scale_ratio: 1.0,
),
"cyri": Font (
asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended",
scale_ratio: 0.9,
),
},
string_map: {
// Common texts used in multiple locations
"common.username": "pseudo",
@ -16,7 +38,7 @@ VoxygenLocalization(
"common.languages": "Langues",
"common.interface": "Interface",
"common.gameplay": "Gameplay",
"common.controls": "Controles",
"common.controls": "Contrôles",
"common.video": "Video",
"common.sound": "Audio",
"common.resume": "Reprendre",

View File

@ -3,9 +3,9 @@ uniform sampler2D t_noise;
const float CLOUD_AVG_HEIGHT = 1025.0;
const float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0;
const float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 50.0;
const float CLOUD_THRESHOLD = 0.3;
const float CLOUD_THRESHOLD = 0.25;
const float CLOUD_SCALE = 5.0;
const float CLOUD_DENSITY = 80.0;
const float CLOUD_DENSITY = 100.0;
float vsum(vec3 v) {
return v.x + v.y + v.z;
@ -21,22 +21,27 @@ vec2 cloud_at(vec3 pos) {
float value = (
0.0
+ texture(t_noise, scaled_pos * 0.0003 + tick_offs).x
+ texture(t_noise, scaled_pos * 0.0009 - tick_offs).x * 0.5
+ texture(t_noise, scaled_pos * 0.0025 - time_of_day.x * 0.0002).x * 0.25
+ texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.15
+ texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.1
+ texture(t_noise, scaled_pos * 0.0015 - tick_offs * 2.0).x * 0.5
//+ texture(t_noise, scaled_pos * 0.0025 - time_of_day.x * 0.0002).x * 0.25
//+ texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.15
//+ texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.2
) / 3.0;
value += (0.0
+ texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.25
+ texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.15
) * value;
float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - CLOUD_AVG_HEIGHT) / 400.0, 0.0) * CLOUD_DENSITY;
const float SHADE_GRADIENT = 1.8 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN);
float shade = ((pos.z - CLOUD_AVG_HEIGHT) * SHADE_GRADIENT + 0.5);
const float SHADE_GRADIENT = 1.5 / (CLOUD_AVG_HEIGHT - CLOUD_HEIGHT_MIN);
float shade = ((pos.z - CLOUD_AVG_HEIGHT) / (CLOUD_HEIGHT_MAX - CLOUD_HEIGHT_MIN)) * 2.5 + 0.7;
return vec2(shade, density / (1.0 + vsum(abs(pos - cam_pos.xyz)) / 5000));
}
vec4 get_cloud_color(vec3 dir, vec3 origin, float time_of_day, float max_dist, float quality) {
const int ITERS = 10;
const int ITERS = 12;
const float INCR = 1.0 / ITERS;
float mind = (CLOUD_HEIGHT_MIN - origin.z) / dir.z;
@ -61,6 +66,10 @@ vec4 get_cloud_color(vec3 dir, vec3 origin, float time_of_day, float max_dist, f
passthrough *= 1.0 - integral;
cloud_shade = mix(cloud_shade, sample.x, passthrough * integral);
dist += INCR * delta;
if (passthrough < 0.05 || (passthrough > 0.8 && dist > (maxd + mind) * 0.7)) {
break;
}
}
}

View File

@ -12,4 +12,5 @@ uniform u_globals {
uvec4 light_shadow_count;
uvec4 medium;
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 moon_light = get_moon_brightness(moon_dir);
// clamp() changed to max() as sun_dir.z is produced from a cos() function and therefore never greater than 1
vec3 sun_color = get_sun_color(sun_dir);
vec3 moon_color = get_moon_color(moon_dir);
vec3 sun_chroma = sun_color * sun_light;
@ -139,7 +136,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, vec3 origin, vec3 f_pos, float q
vec3 moon_halo = pow(max(dot(dir, -moon_dir) + 0.1, 0.0), 8.0) * MOON_HALO_COLOR;
vec3 moon_surf = pow(max(dot(dir, -moon_dir) - 0.001, 0.0), 3000.0) * MOON_SURF_COLOR;
vec3 moon_light = clamp(moon_halo + moon_surf, vec3(0), vec3(clamp(dir.z * 3.0, 0, 1)));
vec3 moon_light = clamp(moon_halo + moon_surf, vec3(0), vec3(max(dir.z * 3.0, 0)));
// Replaced all clamp(sun_dir, 0, 1) with max(sun_dir, 0) because sun_dir is calculated from sin and cos, which are never > 1
@ -188,7 +185,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, vec3 origin, vec3 f_pos, float q
// Clouds
clouds = get_cloud_color(dir, origin, time_of_day, f_dist, quality);
clouds.rgb *= get_sun_brightness(sun_dir) * (sun_halo * 2.5 + get_sun_color(sun_dir)) + get_moon_brightness(moon_dir) * (moon_halo * 80.0 + get_moon_color(moon_dir));
clouds.rgb *= get_sun_brightness(sun_dir) * (sun_halo * 1.5 + get_sun_color(sun_dir)) + get_moon_brightness(moon_dir) * (moon_halo * 80.0 + get_moon_color(moon_dir));
if (f_dist > 5000.0) {
sky_color += sun_light + moon_light;

View File

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

View File

@ -20,7 +20,8 @@ void main() {
if (f_mode == uint(0)) {
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
// Image
} else if (f_mode == uint(1)) {
// HACK: bit 0 is set for both ordinary and north-facing images.
} else if ((f_mode & uint(1)) == uint(1)) {
tgt_color = f_color * texture(u_tex, f_uv);
// 2D Geometry
} else if (f_mode == uint(2)) {

View File

@ -4,6 +4,7 @@
in vec2 v_pos;
in vec2 v_uv;
in vec2 v_center;
in vec4 v_color;
in uint v_mode;
@ -19,15 +20,31 @@ flat out uint f_mode;
out vec4 f_color;
void main() {
f_uv = v_uv;
f_color = v_color;
if (w_pos.w == 1.0) {
f_uv = v_uv;
// Fixed scale In-game element
vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0);
gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0);
} else if (v_mode == uint(3)) {
// HACK: North facing source rectangle.
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y);
f_uv = v_center + look_at * (v_uv - v_center);
gl_Position = vec4(v_pos, 0.0, 1.0);
} else if (v_mode == uint(5)) {
// HACK: North facing target rectangle.
f_uv = v_uv;
float aspect_ratio = screen_res.x / screen_res.y;
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
mat2 look_at = mat2(look_at_dir.y, -look_at_dir.x, look_at_dir.x, look_at_dir.y);
vec2 v_len = v_pos - v_center;
vec2 v_proj = look_at * vec2(v_len.x, v_len.y / aspect_ratio);
gl_Position = vec4(v_center + vec2(v_proj.x, v_proj.y * aspect_ratio), 0.0, 1.0);
} else {
// Interface element
f_uv = v_uv;
gl_Position = vec4(v_pos, 0.0, 1.0);
}
f_mode = v_mode;

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

Binary file not shown.

View File

@ -8,6 +8,8 @@ use std::{
fn main() {
// Get the current githash
// Note: It will compare commits. As long as the commits do not diverge from the
// server no version change will be detected.
match Command::new("git")
.args(&[
"log",
@ -15,6 +17,7 @@ fn main() {
"1",
"--pretty=format:%h/%cd",
"--date=format:%Y-%m-%d-%H:%M",
"--abbrev=8",
])
.output()
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -57,12 +57,12 @@ impl Input {
/// Whether it's the first frame this input has been in
/// its current state
pub fn is_just_pressed(&self) -> bool { (self.just_changed && self.is_pressed()) }
pub fn is_just_pressed(&self) -> bool { self.just_changed && self.is_pressed() }
/// Whether input has been in current state longer than
/// `DEFAULT_HOLD_DURATION`
pub fn is_held_down(&self) -> bool {
(self.is_pressed() && self.duration >= DEFAULT_HOLD_DURATION)
self.is_pressed() && self.duration >= DEFAULT_HOLD_DURATION
}
/// Whether input has been pressed for longer than `threshold`

View File

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

View File

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

View File

@ -1,6 +1,12 @@
#![deny(unsafe_code)]
#![type_length_limit = "1664759"]
#![feature(trait_alias, arbitrary_enum_discriminant, label_break_value)]
#![feature(
arbitrary_enum_discriminant,
bool_to_option,
label_break_value,
trait_alias,
type_alias_impl_trait
)]
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate log;

View File

@ -384,6 +384,7 @@ mod tests {
}
#[test]
#[ignore]
fn send_recv_huge() {
let (mut postoffice, sock) = create_postoffice::<(), Vec<i32>>(3).unwrap();
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 rand::seq::SliceRandom;
use std::{str::FromStr, sync::Arc};
@ -41,6 +44,10 @@ pub struct BodyNames {
/// NOTE: Deliberately don't (yet?) implement serialize.
#[derive(Clone, Debug, Deserialize)]
pub struct SpeciesNames {
/// The keyword used to refer to this species (e.g. via the command
/// console). Should be unique per species and distinct from all body
/// types (maybe in the future, it will just be unique per body type).
pub keyword: String,
/// The generic name for NPCs of this species.
pub generic: String,
}
@ -56,11 +63,11 @@ impl FromStr for NpcKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
let npc_names_json = &*NPC_NAMES;
let npc_names = &*NPC_NAMES;
ALL_NPCS
.iter()
.copied()
.find(|&npc| npc_names_json[npc].keyword == s)
.find(|&npc| npc_names[npc].keyword == s)
.ok_or(())
}
}
@ -71,3 +78,129 @@ pub fn get_npc_name(npc_type: NpcKind) -> &'static str {
// If no pretty name is found, fall back to the keyword.
names.choose(&mut rand::thread_rng()).unwrap_or(keyword)
}
/// Randomly generates a body associated with this NPC kind.
pub fn kind_to_body(kind: NpcKind) -> Body {
match kind {
NpcKind::Humanoid => comp::humanoid::Body::random().into(),
NpcKind::Pig => comp::quadruped_small::Body::random().into(),
NpcKind::Wolf => comp::quadruped_medium::Body::random().into(),
NpcKind::Duck => comp::bird_medium::Body::random().into(),
NpcKind::Giant => comp::biped_large::Body::random().into(),
NpcKind::Rat => comp::critter::Body::random().into(),
}
}
/// A combination of an NpcKind (representing an outer species to generate), and
/// a function that generates a fresh Body of a species that is part of that
/// NpcKind each time it's called. The reason things are done this way is that
/// when parsing spawn strings, we'd like to be able to randomize features that
/// haven't already been specified; for instance, if no species is specified we
/// should randomize species, while if a species is specified we can still
/// randomize other attributes like gender or clothing.
///
/// TODO: Now that we return a closure, consider having the closure accept a
/// source of randomness explicitly, rather than always using ThreadRng.
pub struct NpcBody(pub NpcKind, pub Box<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) {
if ecs_data.inputs.primary.is_pressed() || ecs_data.inputs.secondary.is_pressed() {
if ecs_data.inputs.primary.is_pressed() {
if let Some(Tool(_)) = ecs_data.stats.equipment.main.as_ref().map(|i| &i.kind) {
update.character = CharacterState::Wielding(None);
}

View File

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

View File

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

View File

@ -91,7 +91,7 @@ impl<'a> System<'a> for Sys {
(energy.regen_rate + ENERGY_REGEN_ACCEL * dt.0).min(100.0);
}
},
// All other states do not regen and set the rate back to zero.
// Wield does not regen and sets the rate back to zero.
CharacterState::Wielded(_) => {
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0

View File

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

View File

@ -8,7 +8,7 @@ use common::{
assets, comp,
event::{EventBus, ServerEvent},
msg::{PlayerListUpdate, ServerMsg},
npc::{get_npc_name, NpcKind},
npc::{self, get_npc_name},
state::TimeOfDay,
sync::{Uid, WorldSyncExt},
terrain::TerrainChunkSize,
@ -234,11 +234,18 @@ lazy_static! {
),
ChatCommand::new(
"give_exp",
"{} {}",
"/give_exp <playername> <amount> : Give experience to specified player",
"{d} {}",
"/give_exp <amount> <playername?> : Give experience to yourself or specify a target player",
true,
handle_exp,
),
ChatCommand::new(
"set_level",
"{d} {}",
"/set_level <level> <playername?> : Set own Level or specify a target player",
true,
handle_level
),
ChatCommand::new(
"removelights",
"{}",
@ -462,8 +469,8 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
}
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
(Some(opt_align), Some(id), opt_amount) => {
match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) {
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => {
if let Some(alignment) = parse_alignment(entity, &opt_align) {
let amount = opt_amount
.and_then(|a| a.parse().ok())
@ -487,7 +494,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
10.0,
);
let body = kind_to_body(id);
let body = body();
let new_entity = server
.state
@ -600,17 +607,6 @@ fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option<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) {
let ecs = server.state.ecs();
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) {
let (a_alias, a_exp) = scan_fmt_some!(&args, action.arg_fmt, String, i64);
if let (Some(alias), Some(exp)) = (a_alias, a_exp) {
let ecs = server.state.ecs_mut();
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
fn find_target(
ecs: &specs::World,
opt_alias: Option<String>,
fallback: EcsEntity,
) -> Result<EcsEntity, ServerMsg> {
if let Some(alias) = opt_alias {
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.alias == alias)
.map(|(entity, _)| entity);
.map(|(entity, _)| entity)
.ok_or(ServerMsg::private(format!("Player '{}' not found!", alias)))
} else {
Ok(fallback)
}
}
fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
let (a_exp, a_alias) = scan_fmt_some!(&args, action.arg_fmt, i64, String);
if let Some(exp) = a_exp {
let ecs = server.state.ecs_mut();
let target = find_target(&ecs, a_alias, entity);
let mut error_msg = None;
match opt_player {
Some(_alias) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(entity) {
match target {
Ok(player) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
stats.exp.change_by(exp);
} else {
error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
}
},
_ => {
error_msg = Some(ServerMsg::private(format!("Player '{}' not found!", alias)));
Err(e) => {
error_msg = Some(e);
},
}
if let Some(msg) = error_msg {
server.notify_client(entity, msg);
}
}
}
fn handle_level(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
let (a_lvl, a_alias) = scan_fmt_some!(&args, action.arg_fmt, u32, String);
if let Some(lvl) = a_lvl {
let ecs = server.state.ecs_mut();
let target = find_target(&ecs, a_alias, entity);
let mut error_msg = None;
match target {
Ok(player) => {
if let Some(stats) = ecs.write_storage::<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 cmd;
pub mod error;
pub mod events;
pub mod input;
pub mod metrics;
pub mod settings;
@ -13,7 +14,7 @@ pub mod sys;
#[cfg(not(feature = "worldgen"))] mod test_world;
// Reexports
pub use crate::{error::Error, input::Input, settings::ServerSettings};
pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings};
use crate::{
auth_provider::AuthProvider,
@ -26,19 +27,18 @@ use common::{
assets, comp,
effect::Effect,
event::{EventBus, ServerEvent},
msg::{ClientMsg, ClientState, PlayerListUpdate, ServerError, ServerInfo, ServerMsg},
msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg},
net::PostOffice,
state::{BlockChange, State, TimeOfDay},
sync::{Uid, UidAllocator, WorldSyncExt},
terrain::{block::Block, TerrainChunkSize, TerrainGrid},
vol::{ReadVol, RectVolSize, Vox},
state::{State, TimeOfDay},
sync::{Uid, WorldSyncExt},
terrain::TerrainChunkSize,
vol::{ReadVol, RectVolSize},
};
use log::{debug, error, warn};
use metrics::ServerMetrics;
use rand::Rng;
use specs::{
join::Join, saveload::MarkerAllocator, world::EntityBuilder as EcsEntityBuilder, Builder,
Entity as EcsEntity, RunNow, SystemData, WorldExt,
join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow,
SystemData, WorldExt,
};
use std::{
i32,
@ -57,19 +57,6 @@ use world::{
const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
pub enum Event {
ClientConnected {
entity: EcsEntity,
},
ClientDisconnected {
entity: EcsEntity,
},
Chat {
entity: Option<EcsEntity>,
msg: String,
},
}
#[derive(Copy, Clone)]
struct SpawnPoint(Vec3<f32>);
@ -318,661 +305,6 @@ impl Server {
}
/// 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
/// the given duration.

View File

@ -103,13 +103,14 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
} else {
fn get_npc_name<
'a,
Species,
SpeciesData: core::ops::Index<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,
) -> &str {
&body_data.species[species].generic
) -> &'a str {
&body_data.species[&species].generic
}
const SPAWN_NPCS: &'static [fn() -> (
String,

View File

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

View File

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

View File

@ -6,13 +6,18 @@ pub struct Fader {
volume_to: f32,
is_running: bool,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum FadeDirection {
In,
Out,
}
fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b }
impl Fader {
pub fn fade(time: f32, volume_from: f32, volume_to: f32) -> Self {
pub fn fade(length: f32, volume_from: f32, volume_to: f32) -> Self {
Self {
length: time,
length,
running_time: 0.0,
volume_from,
volume_to,
@ -20,23 +25,28 @@ impl Fader {
}
}
pub fn fade_in(time: f32) -> Self {
Self {
length: time,
running_time: 0.0,
volume_from: 0.0,
volume_to: 1.0,
is_running: true,
pub fn fade_in(time: f32, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
pub fn fade_out(time: f32, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
pub fn update_target_volume(&mut self, volume: f32) {
match self.direction() {
FadeDirection::In => {
self.volume_to = volume;
},
FadeDirection::Out => {
if self.get_volume() > volume {
self.volume_from = volume;
}
},
}
}
pub fn fade_out(time: f32, volume_from: f32) -> Self {
Self {
length: time,
running_time: 0.0,
volume_from,
volume_to: 0.0,
is_running: true,
pub fn direction(&self) -> FadeDirection {
if self.volume_to < self.volume_from {
FadeDirection::Out
} else {
FadeDirection::In
}
}
@ -60,3 +70,107 @@ impl Fader {
pub fn is_finished(&self) -> bool { self.running_time >= self.length || !self.is_running }
}
/// Returns a stopped fader with no running duration
impl Default for Fader {
fn default() -> Self {
Self {
length: 0.0,
running_time: 0.0,
volume_from: 0.0,
volume_to: 1.0,
is_running: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fade_direction_in() {
let fader = Fader::fade_in(10.0, 0.0);
assert_eq!(fader.direction(), FadeDirection::In);
}
#[test]
fn fade_direction_out() {
let fader = Fader::fade_out(10.0, 1.0);
assert_eq!(fader.direction(), FadeDirection::Out);
}
#[test]
fn fade_out_completes() {
let mut fader = Fader::fade_out(10.0, 1.0);
// Run for the full duration
fader.update(10.0);
assert_eq!(fader.get_volume(), 0.0);
assert!(fader.is_finished());
}
#[test]
fn update_target_volume_fading_out_when_currently_above() {
let mut fader = Fader::fade_out(20.0, 1.0);
// After 0.1s, the fader should still be close to 1.0
fader.update(0.1);
// Reduce volume to 0.4. We are currently above that.
fader.update_target_volume(0.4);
// The volume should immediately reduce to < 0.4 on the next update
fader.update(0.1);
assert!(fader.get_volume() < 0.4)
}
#[test]
fn update_target_volume_fading_out_when_currently_below() {
let mut fader = Fader::fade_out(10.0, 0.8);
// After 9s, the fader should be close to 0
fader.update(9.0);
// Notify of a volume increase to 1.0. We are already far below that.
fader.update_target_volume(1.0);
// The fader should be unaffected by the new value, and continue dropping
fader.update(0.1);
assert!(fader.get_volume() < 0.2);
}
#[test]
fn update_target_volume_fading_in_when_currently_above() {
let mut fader = Fader::fade_in(10.0, 1.0);
// After 9s, the fader should be close to 1.0
fader.update(9.0);
// Reduce volume to 0.4. We are currently above that.
fader.update_target_volume(0.4);
// Run out the fader. It's volume should be 0.4
fader.update(1.0);
assert_eq!(fader.get_volume(), 0.4);
}
#[test]
fn update_target_volume_fading_in_when_currently_below() {
let mut fader = Fader::fade_in(20.0, 1.0);
// After 0.1s, the fader should still be close to 0.0
fader.update(0.1);
// Reduce volume to 0.4. The volume_to should be reduced accordingly.
fader.update_target_volume(0.4);
assert_eq!(fader.volume_to, 0.4);
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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