mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge remote-tracking branch 'origin/master' into clientstates
This commit is contained in:
commit
d0439fdd84
60
.github/workflows/artifacts.yml
vendored
Normal file
60
.github/workflows/artifacts.yml
vendored
Normal 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/
|
@ -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
|
||||
|
||||
# --
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -10,9 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Added music system
|
||||
- Added zoomable and rotatable minimap
|
||||
- Added rotating orientation marker to main-map
|
||||
- Added daily Mac builds
|
||||
- Allow spawning individual pet species, not just generic body kinds.
|
||||
- Configurable fonts
|
||||
- Tanslation status tracking
|
||||
- Added gamma setting
|
||||
- Added new orc hairstyles
|
||||
- Added sfx for wielding/unwielding weapons
|
||||
|
||||
### Changed
|
||||
|
||||
- Brighter / higher contrast main-map
|
||||
- Removed highlighting of non-collectible sprites
|
||||
- Fixed /give_exp ignoring player argument
|
||||
- Extend run sfx to small animals to prevent sneak attacks by geese.
|
||||
|
||||
### Removed
|
||||
|
||||
## [0.5.0] - 2019-01-31
|
||||
|
177
Cargo.lock
generated
177
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,17 @@
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Wield(Sword): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.sword_out",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Unwield(Sword): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.sword_in",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
}
|
||||
)
|
BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/button.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/button.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/button_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_mmap_closed_hover.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/button_mmap_closed_hover.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_mmap_closed_press.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/button_mmap_closed_press.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_mmap_open.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/button_mmap_open.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_mmap_open_hover.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/button_mmap_open_hover.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_mmap_open_press.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/button_mmap_open_press.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_press.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/button_press.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/indicator_mmap_small.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/indicator_mmap_small.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/map_indicator.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/map_indicator.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/banner.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/banner.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/banner_small_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/banner_small_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/banner_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/banner_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/enemybar-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar.png
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/enemybar.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/enemybar_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/esc_menu.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/esc_menu.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/mmap.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/mmap.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/elf_m.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/elf_m.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/textbox_bot.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/textbox_bot.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/textbox_mid.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/textbox_mid.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/textbox_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/textbox_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,4 +12,5 @@ uniform u_globals {
|
||||
uvec4 light_shadow_count;
|
||||
uvec4 medium;
|
||||
ivec4 select_pos;
|
||||
vec4 gamma;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/beard/human/human-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/beard/human/human-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/elf/male-2.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/elf/male-2.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/human/male-20.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/human/male-20.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-0.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-1.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-2.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-2.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-3.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-3.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-4.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-4.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-5.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-5.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-6.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-6.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/orc/female.vox
(Stored with Git LFS)
Binary file not shown.
@ -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: [
|
||||
|
BIN
assets/world/structure/dungeon/meso_sewer_temple.vox
(Stored with Git LFS)
BIN
assets/world/structure/dungeon/meso_sewer_temple.vox
(Stored with Git LFS)
Binary file not shown.
@ -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()
|
||||
{
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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`
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use specs::{
|
||||
};
|
||||
|
||||
const CHARGE_COST: i32 = 200;
|
||||
const ROLL_COST: i32 = 30;
|
||||
|
||||
pub struct Sys;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1 +1 @@
|
||||
nightly-2020-01-18
|
||||
nightly-2020-02-06
|
||||
|
@ -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);
|
||||
},
|
||||
}
|
||||
|
||||
|
84
server/src/events/entity_creation.rs
Normal file
84
server/src/events/entity_creation.rs
Normal 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();
|
||||
}
|
175
server/src/events/entity_manipulation.rs
Normal file
175
server/src/events/entity_manipulation.rs
Normal 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();
|
||||
}
|
||||
}
|
171
server/src/events/interaction.rs
Normal file
171
server/src/events/interaction.rs
Normal 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
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
221
server/src/events/inventory_manip.rs
Normal file
221
server/src/events/inventory_manip.rs
Normal 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
109
server/src/events/mod.rs
Normal 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
|
||||
}
|
||||
}
|
60
server/src/events/player.rs
Normal file
60
server/src/events/player.rs
Normal 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 }
|
||||
}
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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]]
|
||||
|
@ -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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
456
voxygen/src/audio/sfx/event_mapper/movement/tests.rs
Normal file
456
voxygen/src/audio/sfx/event_mapper/movement/tests.rs
Normal 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);
|
||||
}
|
@ -91,7 +91,7 @@ impl SfxMgr {
|
||||
},
|
||||
};
|
||||
|
||||
audio.play_sound(sfx_file, position);
|
||||
audio.play_sfx(sfx_file, position, event.vol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use super::{
|
||||
img_ids::Imgs, Fonts, BROADCAST_COLOR, FACTION_COLOR, GAME_UPDATE_COLOR, GROUP_COLOR,
|
||||
KILL_COLOR, META_COLOR, PRIVATE_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR,
|
||||
img_ids::Imgs, BROADCAST_COLOR, FACTION_COLOR, GAME_UPDATE_COLOR, GROUP_COLOR, KILL_COLOR,
|
||||
META_COLOR, PRIVATE_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR,
|
||||
};
|
||||
use crate::GlobalState;
|
||||
use crate::{ui::fonts::ConrodVoxygenFonts, GlobalState};
|
||||
use client::Event as ClientEvent;
|
||||
use common::{msg::validate_chat_msg, ChatType};
|
||||
use conrod_core::{
|
||||
@ -34,7 +34,7 @@ pub struct Chat<'a> {
|
||||
|
||||
global_state: &'a GlobalState,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
@ -48,7 +48,7 @@ impl<'a> Chat<'a> {
|
||||
new_messages: &'a mut VecDeque<ClientEvent>,
|
||||
global_state: &'a GlobalState,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
) -> Self {
|
||||
Self {
|
||||
new_messages,
|
||||
@ -187,8 +187,8 @@ impl<'a> Widget for Chat<'a> {
|
||||
.restrict_to_height(false)
|
||||
.color(TEXT_COLOR)
|
||||
.line_spacing(2.0)
|
||||
.font_size(15)
|
||||
.font_id(self.fonts.opensans);
|
||||
.font_size(self.fonts.opensans.scale(15))
|
||||
.font_id(self.fonts.opensans.conrod_id);
|
||||
|
||||
if let Some(pos) = self.force_cursor {
|
||||
text_edit = text_edit.cursor_pos(pos);
|
||||
@ -251,8 +251,8 @@ impl<'a> Widget for Chat<'a> {
|
||||
ChatType::Kill => KILL_COLOR,
|
||||
};
|
||||
let text = Text::new(&message)
|
||||
.font_size(15)
|
||||
.font_id(self.fonts.opensans)
|
||||
.font_size(self.fonts.opensans.scale(15))
|
||||
.font_id(self.fonts.opensans.conrod_id)
|
||||
.w(470.0)
|
||||
.color(color)
|
||||
.line_spacing(2.0);
|
||||
@ -270,8 +270,8 @@ impl<'a> Widget for Chat<'a> {
|
||||
// Needs to be larger than the space above.
|
||||
Some(
|
||||
Text::new("")
|
||||
.font_size(6)
|
||||
.font_id(self.fonts.opensans)
|
||||
.font_size(self.fonts.opensans.scale(6))
|
||||
.font_id(self.fonts.opensans.conrod_id)
|
||||
.w(470.0),
|
||||
)
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user