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

This commit is contained in:
timokoesters 2020-02-24 21:34:17 +01:00
commit d46575929a
131 changed files with 4106 additions and 2133 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,
),
}
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

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.

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: [

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

View File

@ -1,14 +1,15 @@
use super::{img_ids::Imgs, settings_window::SettingsTab, Fonts, TEXT_COLOR};
use crate::i18n::VoxygenLocalization;
use super::{img_ids::Imgs, settings_window::SettingsTab, TEXT_COLOR};
use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts};
use conrod_core::{
widget::{self, Button, Image},
widget_ids, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
widget_ids, Color, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
};
widget_ids! {
struct Ids {
esc_bg,
fireplace,
banner_top,
menu_button_1,
menu_button_2,
menu_button_3,
@ -21,7 +22,7 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct EscMenu<'a> {
imgs: &'a Imgs,
_fonts: &'a Fonts,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
#[conrod(common_builder)]
@ -31,12 +32,12 @@ pub struct EscMenu<'a> {
impl<'a> EscMenu<'a> {
pub fn new(
imgs: &'a Imgs,
_fonts: &'a Fonts,
fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
) -> Self {
Self {
imgs,
_fonts,
fonts,
localized_strings,
common: widget::CommonBuilder::default(),
}
@ -72,25 +73,33 @@ impl<'a> Widget for EscMenu<'a> {
let widget::UpdateArgs { state, ui, .. } = args;
Image::new(self.imgs.esc_frame)
.w_h(240.0, 440.0)
.w_h(240.0, 380.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.9)))
.middle_of(ui.window)
.set(state.ids.esc_bg, ui);
Image::new(self.imgs.fireplace)
.w_h(210.0, 60.0)
.mid_top_with_margin_on(state.ids.esc_bg, 15.0)
.set(state.ids.fireplace, ui);
Image::new(self.imgs.banner_top)
.w_h(250.0, 34.0)
.mid_top_with_margin_on(state.ids.esc_bg, -34.0)
.set(state.ids.banner_top, ui);
/*Image::new(self.imgs.fireplace)
.w_h(210.0, 60.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8)))
.mid_top_with_margin_on(state.ids.esc_bg, 5.0)
.set(state.ids.fireplace, ui);*/
// Resume
if Button::image(self.imgs.button)
.mid_bottom_with_margin_on(state.ids.fireplace, -55.0)
.mid_bottom_with_margin_on(state.ids.banner_top, -60.0)
.w_h(210.0, 50.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label(&self.localized_strings.get("common.resume"))
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.set(state.ids.menu_button_1, ui)
.was_clicked()
{
@ -106,7 +115,8 @@ impl<'a> Widget for EscMenu<'a> {
.label(&self.localized_strings.get("common.settings"))
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.set(state.ids.menu_button_2, ui)
.was_clicked()
{
@ -121,7 +131,8 @@ impl<'a> Widget for EscMenu<'a> {
.label(&self.localized_strings.get("common.controls"))
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.set(state.ids.menu_button_3, ui)
.was_clicked()
{
@ -136,7 +147,8 @@ impl<'a> Widget for EscMenu<'a> {
.label(&self.localized_strings.get("common.characters"))
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.set(state.ids.menu_button_4, ui)
.was_clicked()
{
@ -151,7 +163,8 @@ impl<'a> Widget for EscMenu<'a> {
.label(&self.localized_strings.get("esc_menu.logout"))
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.set(state.ids.menu_button_5, ui)
.was_clicked()
{
@ -166,7 +179,8 @@ impl<'a> Widget for EscMenu<'a> {
.label(&self.localized_strings.get("esc_menu.quit_game"))
.label_y(conrod_core::position::Relative::Scalar(3.0))
.label_color(TEXT_COLOR)
.label_font_size(20)
.label_font_size(self.fonts.cyri.scale(20))
.label_font_id(self.fonts.cyri.conrod_id)
.set(state.ids.menu_button_6, ui)
.was_clicked()
{

View File

@ -8,6 +8,13 @@ rotation_image_ids! {
// Tooltip Test
tt_side: "voxygen/element/frames/tt_test_edge",
tt_corner: "voxygen/element/frames/tt_test_corner_tr",
//////////////////////////////////////////////////////////////////////////////////////////////////////
<VoxelPixArtGraphic>
// Minimap
indicator_mmap_small: "voxygen.element.buttons.indicator_mmap_small",
}
}
@ -106,6 +113,9 @@ image_ids! {
////////////////////////////////////////////////////////////////////////
<VoxelPixArtGraphic>
// Esc-Menu
fireplace: "voxygen.element.misc_bg.fireplace",
// Skill Icons
twohsword_m1: "voxygen.element.icons.2hsword_m1",
twohsword_m2: "voxygen.element.icons.2hsword_m2",
@ -131,7 +141,6 @@ image_ids! {
skull_2: "voxygen.element.icons.skull_2",
// Map
map_indicator: "voxygen.element.buttons.map_indicator",
indicator_mmap: "voxygen.element.buttons.indicator_mmap",
indicator_mmap_2: "voxygen.element.buttons.indicator_mmap_2",
indicator_mmap_3: "voxygen.element.buttons.indicator_mmap_3",
@ -169,7 +178,7 @@ image_ids! {
mmap_open_press: "voxygen.element.buttons.button_mmap_open_press",
mmap_plus: "voxygen.element.buttons.min_plus.mmap_button-plus",
mmap_plus_hover: "voxygen.element.buttons.min_plus.mmap_button-plus_hover",
mmap_plus_press: "voxygen.element.buttons.min_plus.mmap_button-plus_hover",
mmap_plus_press: "voxygen.element.buttons.min_plus.mmap_button-plus_press",
mmap_minus: "voxygen.element.buttons.min_plus.mmap_button-min",
mmap_minus_hover: "voxygen.element.buttons.min_plus.mmap_button-min_hover",
mmap_minus_press: "voxygen.element.buttons.min_plus.mmap_button-min_press",
@ -227,11 +236,6 @@ image_ids! {
close_button_hover: "voxygen.element.buttons.x_hover",
close_button_press: "voxygen.element.buttons.x_press",
// Esc-Menu
fireplace: "voxygen.element.misc_bg.fireplace",
button: "voxygen.element.buttons.button",
button_hover: "voxygen.element.buttons.button_hover",
button_press: "voxygen.element.buttons.button_press",
// Items
potion_red: "voxygen.voxel.object.potion_red",
@ -257,8 +261,18 @@ image_ids! {
death_bg: "voxygen.background.death",
hurt_bg: "voxygen.background.hurt",
banner_top: "voxygen.element.frames.banner_top",
// Buttons
button: "voxygen.element.buttons.button",
button_hover: "voxygen.element.buttons.button_hover",
button_press: "voxygen.element.buttons.button_press",
// Enemy Healthbar
enemy_health: "voxygen.element.frames.enemybar",
enemy_health_bg: "voxygen.element.frames.enemybar_bg",
// Enemy Bar Content:
enemy_bar: "voxygen.element.skillbar.enemy_bar_content",
// Spell Book Window

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