mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'james/biome-specific-music' into 'master'
Biome/site specific music and ambient sfx See merge request veloren/veloren!1488
This commit is contained in:
commit
08c12b99f8
@ -38,6 +38,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Upscaling support
|
||||
- Added "Persist Combo from Combo Melee State" when rolling mid-combo
|
||||
- You can no longer spam hammer and bow special when stamina is 0
|
||||
- Biome and site specific music system
|
||||
- Ambient SFX emitted from terrain blocks
|
||||
- Campfire SFX
|
||||
- Wind SFX system
|
||||
|
||||
### Changed
|
||||
|
||||
|
300
Cargo.lock
generated
300
Cargo.lock
generated
@ -56,10 +56,22 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.1.2"
|
||||
name = "alsa"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58"
|
||||
checksum = "44581add1add74ade32aca327b550342359ec00191672c23c1caa3d492b85930"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags",
|
||||
"libc",
|
||||
"nix 0.15.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5a0559bcd3f7a482690d98be41c08a43e92f669b179433e95ddf5e8b8fd36a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
@ -196,6 +208,12 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "1.0.0"
|
||||
@ -275,7 +293,7 @@ dependencies = [
|
||||
"hex",
|
||||
"rust-argon2",
|
||||
"serde_json",
|
||||
"ureq",
|
||||
"ureq 1.4.1",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@ -311,6 +329,21 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.12.3"
|
||||
@ -435,6 +468,12 @@ dependencies = [
|
||||
"iovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.6.4"
|
||||
@ -469,6 +508,12 @@ dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.4.0"
|
||||
@ -643,6 +688,30 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680"
|
||||
dependencies = [
|
||||
"ascii 0.9.3",
|
||||
"byteorder",
|
||||
"either",
|
||||
"memchr",
|
||||
"unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2809f67365382d65fd2b6d9c22577231b954ed27400efeafbe687bda75abcc0b"
|
||||
dependencies = [
|
||||
"bytes 0.5.6",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "conrod_core"
|
||||
version = "0.63.0"
|
||||
@ -885,18 +954,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.11.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b55d55d69f403f62a95bd3c04b431e0aedf5120c70f15d07a8edd234443dd59"
|
||||
checksum = "4919d30839e3924b45b84319997a554db1a56918bc5b2a08a6c29886e65e2dca"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"alsa",
|
||||
"core-foundation-sys 0.6.2",
|
||||
"coreaudio-rs",
|
||||
"jni 0.17.0",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"num-traits 0.2.12",
|
||||
"mach 0.3.2",
|
||||
"ndk",
|
||||
"ndk-glue",
|
||||
"nix 0.15.0",
|
||||
"oboe",
|
||||
"parking_lot 0.9.0",
|
||||
"stdweb 0.1.3",
|
||||
"thiserror",
|
||||
"web-sys",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@ -1490,6 +1567,17 @@ dependencies = [
|
||||
"syn 1.0.42",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fetch_unroll"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5c55005e95bbe15f5f72a73b6597d0dc82ddc97ffe2ca097a99dcd591fefbca"
|
||||
dependencies = [
|
||||
"libflate",
|
||||
"tar",
|
||||
"ureq 0.11.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.12"
|
||||
@ -1980,7 +2068,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"fnv",
|
||||
"futures 0.1.29",
|
||||
"http",
|
||||
@ -2067,7 +2155,7 @@ version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
@ -2078,7 +2166,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"futures 0.1.29",
|
||||
"http",
|
||||
"tokio-buf",
|
||||
@ -2091,7 +2179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9625f605ddfaf894bf78a544a7b8e31f562dc843654723a49892d9c7e75ac708"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"futures 0.3.5",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
@ -2121,7 +2209,7 @@ version = "0.12.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"futures 0.1.29",
|
||||
"futures-cpupool",
|
||||
"h2",
|
||||
@ -2305,7 +2393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21dcc74995dd4cd090b147e79789f8d65959cbfb5f0b118002db869ea3bd0a0"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.6.2",
|
||||
"mach",
|
||||
"mach 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2332,6 +2420,34 @@ version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1981310da491a4f0f815238097d0d43d8072732b5ae5f8bd0d8eadf5bf245402"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine 3.8.1",
|
||||
"error-chain",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"walkdir 2.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36bcc950632e48b86da402c5c077590583da5ac0d480103611d5374e7c967a3c"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine 4.3.2",
|
||||
"error-chain",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"walkdir 2.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
@ -2416,6 +2532,18 @@ version = "0.2.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"crc32fast",
|
||||
"rle-decode-fast",
|
||||
"take_mut",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.13+1.0.1"
|
||||
@ -2567,6 +2695,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@ -3125,6 +3262,29 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
|
||||
|
||||
[[package]]
|
||||
name = "oboe"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6a13c9fe73346ff3cee5530b8dd9da1ce3e13cb663be210af3938a2a1dd3eab"
|
||||
dependencies = [
|
||||
"jni 0.14.0",
|
||||
"ndk",
|
||||
"ndk-glue",
|
||||
"num-derive",
|
||||
"num-traits 0.2.12",
|
||||
"oboe-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ff7a51600eabe34e189eec5c995a62f151d8d97e5fbca39e87ca738bb99b82"
|
||||
dependencies = [
|
||||
"fetch_unroll",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ogg"
|
||||
version = "0.7.0"
|
||||
@ -3842,14 +4002,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rodio"
|
||||
version = "0.11.0"
|
||||
name = "rle-decode-fast"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73bbf260262fd5501b7a17d6827e0d25c1127e921eb177150a060faf6e217a70"
|
||||
checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
|
||||
|
||||
[[package]]
|
||||
name = "rodio"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9683532495146e98878d4948fa1a1953f584cd923f2a5f5c26b7a8701b56943"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"hound",
|
||||
"lazy_static",
|
||||
"lewton",
|
||||
]
|
||||
|
||||
@ -3859,7 +4024,7 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a58080b7bb83b2ea28c3b7a9a994fd5e310330b7c8ca5258d99b98128ecfe4"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.12.3",
|
||||
"bitflags",
|
||||
"serde",
|
||||
]
|
||||
@ -3882,7 +4047,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.12.3",
|
||||
"blake2b_simd",
|
||||
"constant_time_eq",
|
||||
"crossbeam-utils 0.7.2",
|
||||
@ -3909,13 +4074,26 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e"
|
||||
dependencies = [
|
||||
"base64 0.10.1",
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.12.3",
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
@ -4424,7 +4602,7 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4509,6 +4687,24 @@ dependencies = [
|
||||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
@ -4585,7 +4781,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15ce4fc3c4cdea1a4399bb1819a539195fb69db4bbe0bde5b7c7f18fed412e02"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"ascii 1.0.0",
|
||||
"chrono",
|
||||
"chunked_transfer",
|
||||
"log",
|
||||
@ -4614,7 +4810,7 @@ version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"futures 0.1.29",
|
||||
"mio 0.6.22",
|
||||
"num_cpus",
|
||||
@ -4632,7 +4828,7 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"either",
|
||||
"futures 0.1.29",
|
||||
]
|
||||
@ -4663,7 +4859,7 @@ version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"futures 0.1.29",
|
||||
"log",
|
||||
]
|
||||
@ -4703,7 +4899,7 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.4.12",
|
||||
"futures 0.1.29",
|
||||
"iovec",
|
||||
"mio 0.6.22",
|
||||
@ -4975,26 +5171,52 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "801125e6d1ba6864cf3a5a92cfb2f0b0a3ee73e40602a0cd206ad2f3c040aa96"
|
||||
dependencies = [
|
||||
"base64 0.11.0",
|
||||
"chunked_transfer",
|
||||
"cookie",
|
||||
"lazy_static",
|
||||
"qstring",
|
||||
"rustls 0.16.0",
|
||||
"url 2.1.1",
|
||||
"webpki",
|
||||
"webpki-roots 0.18.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fb6c9aba13a511bcbb7770864c0e9b8392acda0454a71104498a2bb112d701"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.12.3",
|
||||
"chunked_transfer",
|
||||
"lazy_static",
|
||||
"qstring",
|
||||
"rustls",
|
||||
"rustls 0.18.1",
|
||||
"url 2.1.1",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
"webpki-roots 0.20.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5648,6 +5870,15 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.20.0"
|
||||
@ -5804,6 +6035,15 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "0.9.0"
|
||||
|
9
assets/voxygen/audio/ambient.ron
Normal file
9
assets/voxygen/audio/ambient.ron
Normal file
@ -0,0 +1,9 @@
|
||||
(
|
||||
tracks: [
|
||||
(
|
||||
path: "voxygen.audio.ambient.wind",
|
||||
length: 14.5,
|
||||
tag: Wind,
|
||||
),
|
||||
]
|
||||
)
|
BIN
assets/voxygen/audio/ambient/forest_day.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/ambient/forest_day.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/ambient/forest_morning.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/ambient/forest_morning.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/ambient/wind.ogg
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/ambient/wind.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -1,8 +1,69 @@
|
||||
(
|
||||
{
|
||||
//
|
||||
// Ambient
|
||||
//
|
||||
Campfire: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.fire",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Embers: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.embers",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Birdcall: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.birdcall_1",
|
||||
"voxygen.audio.sfx.ambient.birdcall_2",
|
||||
],
|
||||
threshold: 10.0,
|
||||
),
|
||||
Owl: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.owl_1",
|
||||
],
|
||||
threshold: 14.0,
|
||||
),
|
||||
Cricket: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.crickets_1",
|
||||
],
|
||||
threshold: 3.0,
|
||||
),
|
||||
Frog: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.frog_croak_1",
|
||||
],
|
||||
threshold: 4.0,
|
||||
),
|
||||
Bees: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.bees_1",
|
||||
],
|
||||
threshold: 15.0,
|
||||
),
|
||||
RunningWater: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.ambient.running_water_1",
|
||||
],
|
||||
threshold: 7.0,
|
||||
),
|
||||
//
|
||||
// Character States
|
||||
//
|
||||
Swim: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.footsteps.water_splash_1",
|
||||
"voxygen.audio.sfx.footsteps.water_splash_2",
|
||||
"voxygen.audio.sfx.footsteps.water_splash_3",
|
||||
"voxygen.audio.sfx.footsteps.water_splash_4",
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
Run: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_1",
|
||||
@ -14,32 +75,59 @@
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
ExperienceGained: (
|
||||
QuadRun: (
|
||||
files: [
|
||||
// "voxygen.audio.sfx.character.experience_gained_1",
|
||||
// "voxygen.audio.sfx.character.experience_gained_2",
|
||||
// "voxygen.audio.sfx.character.experience_gained_3",
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_1",
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_2",
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_3",
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_4",
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_5",
|
||||
"voxygen.audio.sfx.footsteps.stepgrass_6",
|
||||
],
|
||||
threshold: 0.5,
|
||||
threshold: 0.12,
|
||||
),
|
||||
SnowRun: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.footsteps.snow_step_1",
|
||||
"voxygen.audio.sfx.footsteps.snow_step_2",
|
||||
"voxygen.audio.sfx.footsteps.snow_step_3",
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
QuadSnowRun: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.footsteps.snow_step_1",
|
||||
"voxygen.audio.sfx.footsteps.snow_step_2",
|
||||
"voxygen.audio.sfx.footsteps.snow_step_3",
|
||||
],
|
||||
threshold: 0.12,
|
||||
),
|
||||
//ExperienceGained: (
|
||||
// files: [
|
||||
// "voxygen.audio.sfx.character.experience_gained_1",
|
||||
// "voxygen.audio.sfx.character.experience_gained_2",
|
||||
// "voxygen.audio.sfx.character.experience_gained_3",
|
||||
// ],
|
||||
// threshold: 0.5,
|
||||
//),
|
||||
LevelUp:(
|
||||
files: [
|
||||
"voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Jump: (
|
||||
files: [
|
||||
// Event not implemented?
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
Fall: (
|
||||
files: [
|
||||
// Event not implemented?
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
//Jump: (
|
||||
// files: [
|
||||
// // Event not implemented?
|
||||
// ],
|
||||
// threshold: 0.25,
|
||||
//),
|
||||
//Fall: (
|
||||
// files: [
|
||||
// // Event not implemented?
|
||||
// ],
|
||||
// threshold: 0.25,
|
||||
//),
|
||||
Roll: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.character.dive_roll_1",
|
||||
@ -47,30 +135,30 @@
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
Climb: (
|
||||
files: [
|
||||
// TODO: sync with animation
|
||||
//"voxygen.audio.sfx.footsteps.stepgrass_1",
|
||||
//"voxygen.audio.sfx.footsteps.stepgrass_2",
|
||||
//"voxygen.audio.sfx.footsteps.stepgrass_3",
|
||||
//"voxygen.audio.sfx.footsteps.stepgrass_4",
|
||||
//"voxygen.audio.sfx.footsteps.stepgrass_5",
|
||||
//"voxygen.audio.sfx.footsteps.stepgrass_6",
|
||||
],
|
||||
threshold: 0.25,
|
||||
),
|
||||
//Climb: (
|
||||
// files: [
|
||||
// TODO: sync with animation
|
||||
// "voxygen.audio.sfx.footsteps.stepgrass_1",
|
||||
// "voxygen.audio.sfx.footsteps.stepgrass_2",
|
||||
// "voxygen.audio.sfx.footsteps.stepgrass_3",
|
||||
// "voxygen.audio.sfx.footsteps.stepgrass_4",
|
||||
// "voxygen.audio.sfx.footsteps.stepgrass_5",
|
||||
// "voxygen.audio.sfx.footsteps.stepgrass_6",
|
||||
// ],
|
||||
// threshold: 0.25,
|
||||
//),
|
||||
GliderOpen: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.glider_open",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Glide: (
|
||||
files: [
|
||||
// Event Missing or not implemented?
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
//Glide: (
|
||||
// files: [
|
||||
// // Event Missing or not implemented?
|
||||
// ],
|
||||
// threshold: 0.5,
|
||||
//),
|
||||
GliderClose: (
|
||||
files: [
|
||||
"voxygen.audio.sfx.glider_close",
|
||||
@ -145,7 +233,7 @@
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Attack(BasicMelee, Hammer): (
|
||||
Attack(ComboMelee(Swing, 1), Hammer): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.abilities.swing",
|
||||
],
|
||||
@ -185,7 +273,13 @@
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Attack(BasicMelee, Axe): (
|
||||
Attack(ComboMelee(Swing, 1), Axe): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.abilities.swing",
|
||||
],
|
||||
threshold: 0.7,
|
||||
),
|
||||
Attack(ComboMelee(Swing, 2), Axe): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.abilities.swing",
|
||||
],
|
||||
@ -211,32 +305,32 @@
|
||||
),
|
||||
|
||||
//
|
||||
// Fire Rod / Regeneration Staff
|
||||
// Fire Staff
|
||||
//
|
||||
Wield(Staff): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.staff_out",
|
||||
"voxygen.audio.sfx.weapon.sword_out",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Unwield(Staff): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.staff_in",
|
||||
"voxygen.audio.sfx.weapon.sword_in",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Attack(BasicMelee, Staff): (
|
||||
Attack(BasicBeam, Staff): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.abilities.swing",
|
||||
"voxygen.audio.sfx.abilities.flame_thrower",
|
||||
],
|
||||
threshold: 0.8,
|
||||
),
|
||||
Attack(BasicRanged, Staff): (
|
||||
files: [
|
||||
// "voxygen.audio.sfx.abilities.staff_channeling",
|
||||
],
|
||||
threshold: 0.8,
|
||||
threshold: 0.2,
|
||||
),
|
||||
//Attack(BasicRanged, Staff): (
|
||||
// files: [
|
||||
// "voxygen.audio.sfx.abilities.staff_channeling",
|
||||
// ],
|
||||
// threshold: 0.8,
|
||||
//),
|
||||
Inventory(CollectedTool(Staff)): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.inventory.pickup_staff",
|
||||
@ -259,12 +353,12 @@
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Attack(BasicRanged, Bow): (
|
||||
files: [
|
||||
// channeling sound.
|
||||
],
|
||||
threshold: 0.8,
|
||||
),
|
||||
//Attack(BasicRanged, Bow): (
|
||||
// files: [
|
||||
// // channeling sound.
|
||||
// ],
|
||||
// threshold: 0.8,
|
||||
//),
|
||||
Inventory(CollectedTool(Bow)): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.inventory.add_item",
|
||||
@ -272,6 +366,22 @@
|
||||
threshold: 0.3,
|
||||
),
|
||||
|
||||
//
|
||||
// Sceptre
|
||||
//
|
||||
Wield(Sceptre): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.sword_out",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
Unwield(Sceptre): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.sword_in",
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
|
||||
//
|
||||
// Dagger
|
||||
//
|
||||
@ -343,6 +453,12 @@
|
||||
],
|
||||
threshold: 0.3,
|
||||
),
|
||||
Inventory(CollectedItem("ShinyGem")): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.weapon.staff_out",
|
||||
],
|
||||
threshold: 0.3,
|
||||
),
|
||||
Inventory(CollectFailed): (
|
||||
files: [
|
||||
"voxygen.audio.sfx.inventory.add_failed",
|
||||
@ -461,17 +577,17 @@
|
||||
],
|
||||
threshold: 0.3,
|
||||
),
|
||||
Explosion: (
|
||||
files: [
|
||||
// in code
|
||||
],
|
||||
threshold: 0.2,
|
||||
),
|
||||
ProjectileShot: (
|
||||
files: [
|
||||
// in code
|
||||
],
|
||||
threshold: 0.5,
|
||||
),
|
||||
//Explosion: (
|
||||
// files: [
|
||||
// // in code
|
||||
// ],
|
||||
// threshold: 0.2,
|
||||
//),
|
||||
//ProjectileShot: (
|
||||
// files: [
|
||||
// // in code
|
||||
// ],
|
||||
// threshold: 0.5,
|
||||
//),
|
||||
}
|
||||
)
|
||||
|
BIN
assets/voxygen/audio/sfx/abilities/flame_thrower.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/abilities/flame_thrower.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/abilities/staff_channeling.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/abilities/staff_channeling.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/bees_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/bees_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/birdcall_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/birdcall_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/birdcall_2.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/birdcall_2.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/crickets_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/crickets_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/embers.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/embers.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/fire.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/fire.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/frog_croak_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/frog_croak_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/owl_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/owl_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/running_water_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/running_water_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/character/dive_roll_1.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/character/dive_roll_1.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/character/dive_roll_2.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/character/dive_roll_2.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/snow_step_1.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/snow_step_1.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/snow_step_2.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/snow_step_2.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/snow_step_3.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/snow_step_3.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_1.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_1.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_2.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_2.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_3.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_3.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_4.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/footsteps/water_splash_4.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_2.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_2.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_3.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_3.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_4.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/footsteps/wood_step_4.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -1,119 +1,194 @@
|
||||
// TODO: Re-add tunes that are not fitting general outside day/night situations
|
||||
// TODO: Add an ambient-soundtrack that runs independently from the musical soundtrack
|
||||
|
||||
(
|
||||
tracks: [
|
||||
(
|
||||
title: "Forest Day", // Ambience Track
|
||||
path: "voxygen.audio.ambient.forest_day",
|
||||
length: 629.0,
|
||||
timing: Some(Day),
|
||||
artist: "https://www.youtube.com/watch?v=FwVTkB-BIvM",
|
||||
),
|
||||
(
|
||||
title: "A Solemn Quest",
|
||||
path: "voxygen.audio.soundtrack.a_solemn_quest",
|
||||
length: 206.0,
|
||||
timing: Some(Night),
|
||||
artist: "Eden",
|
||||
),
|
||||
(
|
||||
title: "Into The Dark Forest",
|
||||
path: "voxygen.audio.soundtrack.into_the_dark_forest",
|
||||
length: 184.0,
|
||||
timing: Some(Night),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Field Grazing",
|
||||
path: "voxygen.audio.soundtrack.field_grazing",
|
||||
length: 154.0,
|
||||
timing: Some(Day),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Wandering Voices",
|
||||
path: "voxygen.audio.soundtrack.wandering_voices",
|
||||
length: 137.0,
|
||||
timing: Some(Night),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
/*(
|
||||
title: "Snowtop Volume", //Snow Region
|
||||
path: "voxygen.audio.soundtrack.snowtop_volume",
|
||||
length: 89.0,
|
||||
timing: Some(Day),
|
||||
artist: "Aeronic",
|
||||
),*/
|
||||
(
|
||||
title: "Mineral Deposits",
|
||||
path: "voxygen.audio.soundtrack.mineral_deposits",
|
||||
length: 148.0,
|
||||
timing: Some(Day),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Moonbeams",
|
||||
path: "voxygen.audio.soundtrack.moonbeams",
|
||||
length: 158.0,
|
||||
timing: Some(Night),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Serene Meadows",
|
||||
path: "voxygen.audio.soundtrack.serene_meadows",
|
||||
length: 173.0,
|
||||
timing: Some(Night),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
/*(
|
||||
title: "Rest Assured", // Town/Shop
|
||||
path: "voxygen.audio.soundtrack.rest_assured",
|
||||
length: 185.0,
|
||||
timing: Some(Day),
|
||||
artist: "badbbad",
|
||||
),*/
|
||||
(
|
||||
title: "Just The Beginning",
|
||||
path: "voxygen.audio.soundtrack.just_the_beginning",
|
||||
length: 188.0,
|
||||
timing: Some(Day),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Campfire Stories",
|
||||
path: "voxygen.audio.soundtrack.campfire_stories",
|
||||
length: 100.0,
|
||||
timing: Some(Night),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Limits",
|
||||
path: "voxygen.audio.soundtrack.limits",
|
||||
length: 203.0,
|
||||
timing: Some(Night),
|
||||
artist: "badbbad",
|
||||
),
|
||||
/*( // Dungeon
|
||||
title: "Down The Rabbit Hole",
|
||||
path: "voxygen.audio.soundtrack.down_the_rabbit_hole",
|
||||
length: 244.0,
|
||||
timing: Some(Night),
|
||||
artist: "badbbad",
|
||||
),*/
|
||||
(
|
||||
title: "Between The Fairies",
|
||||
path: "voxygen.audio.soundtrack.between_the_fairies",
|
||||
length: 175.0,
|
||||
timing: Some(Night),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Forest Morning", // Ambience Track
|
||||
path: "voxygen.audio.ambient.forest_morning",
|
||||
length: 600.0,
|
||||
timing: Some(Day),
|
||||
artist: "https://www.youtube.com/watch?v=eq4nfIdK6C4",
|
||||
),
|
||||
]
|
||||
)
|
||||
// TODO: Re-add tunes that are not fitting general outside day/night situations
|
||||
// TODO: Add an ambient-soundtrack that runs independently from the musical soundtrack
|
||||
// Times: Some(Day), Some(Night), None
|
||||
// List of biomes currently: Grassland, Forest, Desert, Snowland, Lake, Mountain, Ocean
|
||||
// Also Jungle and Swamp but these are not defined currently as the worldgen around
|
||||
// them is changing and not stable
|
||||
// Sites: Cave, Dungeon
|
||||
|
||||
(
|
||||
tracks: [
|
||||
(
|
||||
title: "Oceania",
|
||||
path: "voxygen.audio.soundtrack.oceania",
|
||||
length: 135.0,
|
||||
timing: None,
|
||||
biomes: [
|
||||
(Lake, 2),
|
||||
(Ocean, 3),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "Eden",
|
||||
),
|
||||
(
|
||||
title: "A Solemn Quest",
|
||||
path: "voxygen.audio.soundtrack.a_solemn_quest",
|
||||
length: 206.0,
|
||||
timing: Some(Night),
|
||||
biomes: [
|
||||
(Desert, 1),
|
||||
(Grassland, 1),
|
||||
(Snowland, 1),
|
||||
(Mountain, 1),
|
||||
(Lake, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "Eden",
|
||||
),
|
||||
(
|
||||
title: "Into The Dark Forest",
|
||||
path: "voxygen.audio.soundtrack.into_the_dark_forest",
|
||||
length: 184.0,
|
||||
timing: Some(Night),
|
||||
biomes: [
|
||||
(Forest, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Field Grazing",
|
||||
path: "voxygen.audio.soundtrack.field_grazing",
|
||||
length: 154.0,
|
||||
timing: Some(Day),
|
||||
biomes: [
|
||||
(Grassland, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Wandering Voices",
|
||||
path: "voxygen.audio.soundtrack.wandering_voices",
|
||||
length: 137.0,
|
||||
timing: Some(Night),
|
||||
biomes: [],
|
||||
site: Some(Void),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Snowtop Volume",
|
||||
path: "voxygen.audio.soundtrack.snowtop_volume",
|
||||
length: 89.0,
|
||||
timing: None,
|
||||
biomes: [
|
||||
(Snowland, 1),
|
||||
(Mountain, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Mineral Deposits",
|
||||
path: "voxygen.audio.soundtrack.mineral_deposits",
|
||||
length: 148.0,
|
||||
timing: None,
|
||||
biomes: [],
|
||||
site: Some(Cave),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
( //Repeat for other site
|
||||
title: "Mineral Deposits",
|
||||
path: "voxygen.audio.soundtrack.mineral_deposits",
|
||||
length: 148.0,
|
||||
timing: None,
|
||||
biomes: [],
|
||||
site: Some(Dungeon),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Moonbeams",
|
||||
path: "voxygen.audio.soundtrack.moonbeams",
|
||||
length: 158.0,
|
||||
timing: Some(Night),
|
||||
biomes: [],
|
||||
site: Some(Void),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
(
|
||||
title: "Serene Meadows",
|
||||
path: "voxygen.audio.soundtrack.serene_meadows",
|
||||
length: 173.0,
|
||||
timing: Some(Night),
|
||||
biomes: [
|
||||
(Grassland, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "Aeronic",
|
||||
),
|
||||
//(
|
||||
// title: "Rest Assured", // Town/Shop
|
||||
// path: "voxygen.audio.soundtrack.rest_assured",
|
||||
// length: 185.0,
|
||||
// timing: Some(Day),
|
||||
// biomes: [],
|
||||
// site: Some(Void),
|
||||
// artist: "badbbad",
|
||||
//),
|
||||
(
|
||||
title: "Just The Beginning",
|
||||
path: "voxygen.audio.soundtrack.just_the_beginning",
|
||||
length: 188.0,
|
||||
timing: Some(Day),
|
||||
biomes: [
|
||||
(Grassland, 1),
|
||||
(Snowland, 1),
|
||||
(Mountain, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Campfire Stories",
|
||||
path: "voxygen.audio.soundtrack.campfire_stories",
|
||||
length: 100.0,
|
||||
timing: Some(Night),
|
||||
biomes: [],
|
||||
site: Some(Void),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Limits",
|
||||
path: "voxygen.audio.soundtrack.limits",
|
||||
length: 203.0,
|
||||
timing: None,
|
||||
biomes: [
|
||||
(Desert, 1),
|
||||
(Lake, 1)
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Down The Rabbit Hole",
|
||||
path: "voxygen.audio.soundtrack.down_the_rabbit_hole",
|
||||
length: 244.0,
|
||||
timing: None,
|
||||
biomes: [],
|
||||
site: Some(Cave),
|
||||
artist: "badbbad",
|
||||
),
|
||||
( //Repeat for other site
|
||||
title: "Down The Rabbit Hole",
|
||||
path: "voxygen.audio.soundtrack.down_the_rabbit_hole",
|
||||
length: 244.0,
|
||||
timing: None,
|
||||
biomes: [],
|
||||
site: Some(Dungeon),
|
||||
artist: "badbbad",
|
||||
),
|
||||
(
|
||||
title: "Between The Fairies",
|
||||
path: "voxygen.audio.soundtrack.between_the_fairies",
|
||||
length: 175.0,
|
||||
timing: Some(Night),
|
||||
biomes: [
|
||||
(Forest, 1),
|
||||
(Lake, 1),
|
||||
(Snowland, 1),
|
||||
],
|
||||
site: Some(Void),
|
||||
artist: "badbbad",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
BIN
assets/voxygen/audio/soundtrack/a_solemn_quest.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/a_solemn_quest.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/between_the_fairies.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/between_the_fairies.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/campfire_stories.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/campfire_stories.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/down_the_rabbit_hole.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/down_the_rabbit_hole.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/field_grazing.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/field_grazing.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/into_the_dark_forest.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/into_the_dark_forest.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/just_the_beginning.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/just_the_beginning.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/limits.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/limits.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/mineral_deposits.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/mineral_deposits.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/moonbeams.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/moonbeams.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/oceania.ogg
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/soundtrack/oceania.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/rest_assured.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/rest_assured.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/serene_meadows.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/serene_meadows.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/snowtop_volume.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/snowtop_volume.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/veloren_title_tune.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/veloren_title_tune.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/wandering_voices.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/wandering_voices.ogg
(Stored with Git LFS)
Binary file not shown.
@ -34,7 +34,7 @@ use common::{
|
||||
recipe::RecipeBook,
|
||||
state::State,
|
||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||
terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize},
|
||||
terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use comp::BuffKind;
|
||||
@ -46,6 +46,7 @@ use image::DynamicImage;
|
||||
use network::{Network, Participant, Pid, ProtocolAddr, Stream};
|
||||
use num::traits::FloatConst;
|
||||
use rayon::prelude::*;
|
||||
use specs::Component;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
net::SocketAddr,
|
||||
@ -857,6 +858,40 @@ impl Client {
|
||||
self.state.terrain().get_key_arc(chunk_pos).cloned()
|
||||
}
|
||||
|
||||
pub fn current<C: Component>(&self) -> Option<C>
|
||||
where
|
||||
C: Clone,
|
||||
{
|
||||
Some(self.state.read_storage::<C>().get(self.entity).cloned()?)
|
||||
}
|
||||
|
||||
pub fn current_biome(&self) -> BiomeKind {
|
||||
match self.current_chunk() {
|
||||
Some(chunk) => chunk.meta().biome(),
|
||||
_ => BiomeKind::Void,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_site(&self) -> SitesKind {
|
||||
let mut player_alt = 0.0;
|
||||
if let Some(position) = self.current::<comp::Pos>() {
|
||||
player_alt = position.0.z;
|
||||
}
|
||||
let mut contains_cave = false;
|
||||
let mut terrain_alt = 0.0;
|
||||
if let Some(chunk) = self.current_chunk() {
|
||||
terrain_alt = chunk.meta().alt();
|
||||
contains_cave = chunk.meta().contains_cave();
|
||||
}
|
||||
if player_alt < (terrain_alt - 15.0) && contains_cave {
|
||||
SitesKind::Cave
|
||||
} else if player_alt < (terrain_alt - 15.0) {
|
||||
SitesKind::Dungeon
|
||||
} else {
|
||||
SitesKind::Void
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inventories(&self) -> ReadStorage<comp::Inventory> { self.state.read_storage() }
|
||||
|
||||
pub fn loadouts(&self) -> ReadStorage<comp::Loadout> { self.state.read_storage() }
|
||||
|
@ -22,6 +22,13 @@ pub enum Outcome {
|
||||
body: comp::Body,
|
||||
vel: Vec3<f32>,
|
||||
},
|
||||
LevelUp {
|
||||
pos: Vec3<f32>,
|
||||
},
|
||||
Beam {
|
||||
pos: Vec3<f32>,
|
||||
heal: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
@ -29,6 +36,8 @@ impl Outcome {
|
||||
match self {
|
||||
Outcome::Explosion { pos, .. } => Some(*pos),
|
||||
Outcome::ProjectileShot { pos, .. } => Some(*pos),
|
||||
Outcome::LevelUp { pos } => Some(*pos),
|
||||
Outcome::Beam { pos, .. } => Some(*pos),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ impl<'a> System<'a> for Sys {
|
||||
let start_time = std::time::Instant::now();
|
||||
span!(_guard, "run", "melee::Sys::run");
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut _local_emitter = local_bus.emitter();
|
||||
let _local_emitter = local_bus.emitter();
|
||||
// Attacks
|
||||
for (entity, uid, pos, ori, scale_maybe, attack) in (
|
||||
&entities,
|
||||
|
@ -1,13 +1,15 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum BiomeKind {
|
||||
Void,
|
||||
Lake,
|
||||
Grassland,
|
||||
Ocean,
|
||||
Mountain,
|
||||
Snowlands,
|
||||
Snowland,
|
||||
Desert,
|
||||
Swamp,
|
||||
Jungle,
|
||||
Forest,
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ make_case_elim!(
|
||||
Wood = 0x40,
|
||||
Leaves = 0x41,
|
||||
// 0x42 <= x < 0x50 is reserved for future tree parts
|
||||
|
||||
// Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we
|
||||
// often want to experiment with new kinds of block without allocating them a
|
||||
// dedicated block kind.
|
||||
|
@ -2,6 +2,7 @@ pub mod biome;
|
||||
pub mod block;
|
||||
pub mod chonk;
|
||||
pub mod map;
|
||||
pub mod site;
|
||||
pub mod sprite;
|
||||
pub mod structure;
|
||||
|
||||
@ -10,6 +11,7 @@ pub use self::{
|
||||
biome::BiomeKind,
|
||||
block::{Block, BlockKind},
|
||||
map::MapSizeLg,
|
||||
site::SitesKind,
|
||||
sprite::SpriteKind,
|
||||
structure::Structure,
|
||||
};
|
||||
@ -50,21 +52,53 @@ impl RectVolSize for TerrainChunkSize {
|
||||
pub struct TerrainChunkMeta {
|
||||
name: Option<String>,
|
||||
biome: BiomeKind,
|
||||
alt: f32,
|
||||
tree_density: f32,
|
||||
contains_cave: bool,
|
||||
contains_river: bool,
|
||||
}
|
||||
|
||||
impl TerrainChunkMeta {
|
||||
pub fn new(name: Option<String>, biome: BiomeKind) -> Self { Self { name, biome } }
|
||||
pub fn new(
|
||||
name: Option<String>,
|
||||
biome: BiomeKind,
|
||||
alt: f32,
|
||||
tree_density: f32,
|
||||
contains_cave: bool,
|
||||
contains_river: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
biome,
|
||||
alt,
|
||||
tree_density,
|
||||
contains_cave,
|
||||
contains_river,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn void() -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
biome: BiomeKind::Void,
|
||||
alt: 0.0,
|
||||
tree_density: 0.0,
|
||||
contains_cave: false,
|
||||
contains_river: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str { self.name.as_deref().unwrap_or("Wilderness") }
|
||||
|
||||
pub fn biome(&self) -> BiomeKind { self.biome }
|
||||
|
||||
pub fn alt(&self) -> f32 { self.alt }
|
||||
|
||||
pub fn tree_density(&self) -> f32 { self.tree_density }
|
||||
|
||||
pub fn contains_cave(&self) -> bool { self.contains_cave }
|
||||
|
||||
pub fn contains_river(&self) -> bool { self.contains_river }
|
||||
}
|
||||
|
||||
// Terrain type aliases
|
||||
|
10
common/src/terrain/site.rs
Normal file
10
common/src/terrain/site.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum SitesKind {
|
||||
Dungeon,
|
||||
Cave,
|
||||
Settlement,
|
||||
Castle,
|
||||
Void,
|
||||
}
|
@ -145,6 +145,11 @@ pub fn handle_shockwave(
|
||||
|
||||
pub fn handle_beam(server: &mut Server, properties: beam::Properties, pos: Pos, ori: Ori) {
|
||||
let state = server.state_mut();
|
||||
let ecs = state.ecs();
|
||||
ecs.write_resource::<Vec<Outcome>>().push(Outcome::Beam {
|
||||
pos: pos.0,
|
||||
heal: properties.lifesteal_eff > 0.0,
|
||||
});
|
||||
state.create_beam(properties, pos, ori).build();
|
||||
}
|
||||
|
||||
|
@ -686,14 +686,24 @@ pub fn handle_explosion(
|
||||
}
|
||||
|
||||
pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
|
||||
let ecs = &server.state.ecs();
|
||||
let uids = server.state.ecs().read_storage::<Uid>();
|
||||
let uid = uids
|
||||
.get(entity)
|
||||
.expect("Failed to fetch uid component for entity.");
|
||||
let pos = server
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<Pos>()
|
||||
.get(entity)
|
||||
.expect("Failed to fetch position component for the entity.")
|
||||
.0;
|
||||
|
||||
server.state.notify_players(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::LevelChange(*uid, new_level),
|
||||
));
|
||||
ecs.write_resource::<Vec<Outcome>>()
|
||||
.push(Outcome::LevelUp { pos });
|
||||
}
|
||||
|
||||
pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: buff::BuffChange) {
|
||||
|
@ -58,7 +58,7 @@ server = {package = "veloren-server", path = "../server", optional = true}
|
||||
backtrace = "0.3.40"
|
||||
bincode = "1.2"
|
||||
chrono = "0.4.9"
|
||||
cpal = "0.11"
|
||||
cpal = "0.13"
|
||||
copy_dir = "0.1.2"
|
||||
crossbeam = "=0.7.2"
|
||||
deunicode = "1.0"
|
||||
@ -75,7 +75,7 @@ native-dialog = { version = "0.4.2", default-features = false, optional = true }
|
||||
num = "0.2"
|
||||
ordered-float = { version = "2.0.0", default-features = false }
|
||||
rand = "0.7"
|
||||
rodio = {version = "0.11", default-features = false, features = ["wav", "vorbis"]}
|
||||
rodio = {version = "0.13", default-features = false, features = ["wav", "vorbis"]}
|
||||
ron = {version = "0.6", default-features = false}
|
||||
serde = {version = "1.0", features = [ "rc", "derive" ]}
|
||||
treeculler = "0.1.0"
|
||||
|
145
voxygen/src/audio/ambient.rs
Normal file
145
voxygen/src/audio/ambient.rs
Normal file
@ -0,0 +1,145 @@
|
||||
//! Handles ambient non-positional sounds
|
||||
use crate::{
|
||||
audio::{channel::AmbientChannelTag, AudioFrontend},
|
||||
scene::Camera,
|
||||
};
|
||||
use client::Client;
|
||||
use common::{assets, state::State, vol::ReadVol};
|
||||
use serde::Deserialize;
|
||||
use std::time::Instant;
|
||||
use tracing::warn;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct AmbientCollection {
|
||||
tracks: Vec<AmbientItem>,
|
||||
}
|
||||
|
||||
/// Configuration for a single music track in the soundtrack
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AmbientItem {
|
||||
path: String,
|
||||
/// Length of the track in seconds
|
||||
length: f32,
|
||||
/// Specifies which ambient channel to play on
|
||||
tag: AmbientChannelTag,
|
||||
}
|
||||
|
||||
pub struct AmbientMgr {
|
||||
soundtrack: AmbientCollection,
|
||||
began_playing: Instant,
|
||||
next_track_change: f32,
|
||||
volume: f32,
|
||||
tree_multiplier: f32,
|
||||
}
|
||||
|
||||
impl Default for AmbientMgr {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
soundtrack: Self::load_soundtrack_items(),
|
||||
began_playing: Instant::now(),
|
||||
next_track_change: 0.0,
|
||||
volume: 0.0,
|
||||
tree_multiplier: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AmbientMgr {
|
||||
/// Checks whether the previous track has completed. If so, sends a
|
||||
/// request to play the next (random) track
|
||||
pub fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
client: &Client,
|
||||
camera: &Camera,
|
||||
) {
|
||||
if audio.sfx_enabled() && !self.soundtrack.tracks.is_empty() {
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
|
||||
let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
|
||||
(chunk.meta().alt(), chunk.meta().tree_density())
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
};
|
||||
|
||||
// The following code is specifically for wind, as it is the only
|
||||
// non-positional ambient sound in the game. Others can be added
|
||||
// as seen fit.
|
||||
|
||||
// Wind volume increases with altitude
|
||||
let alt_multiplier = (cam_pos.z / 1200.0).abs();
|
||||
|
||||
// Tree density factors into wind volume. The more trees,
|
||||
// the lower wind volume. The trees make more of an impact
|
||||
// the closer the camera is to the ground.
|
||||
self.tree_multiplier =
|
||||
((1.0 - tree_density) + ((cam_pos.z - terrain_alt) / 150.0).powf(2.0)).min(1.0);
|
||||
|
||||
let mut volume_multiplier = alt_multiplier * self.tree_multiplier;
|
||||
|
||||
// Checks if the camera is underwater to stop ambient sounds
|
||||
if state
|
||||
.terrain()
|
||||
.get((cam_pos).map(|e| e.floor() as i32))
|
||||
.map(|b| b.is_liquid())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
volume_multiplier *= 0.1;
|
||||
}
|
||||
if cam_pos.z < terrain_alt - 10.0 {
|
||||
volume_multiplier = 0.0;
|
||||
}
|
||||
|
||||
let target_volume = volume_multiplier.clamped(0.0, 1.0);
|
||||
|
||||
// Transitions the ambient sounds (more) smoothly
|
||||
self.volume = audio.get_ambient_volume();
|
||||
audio.set_ambient_volume(Lerp::lerp(self.volume, target_volume, 0.01));
|
||||
|
||||
if self.began_playing.elapsed().as_secs_f32() > self.next_track_change {
|
||||
// Right now there is only wind non-positional sfx so it is always
|
||||
// selected. Modify this variable assignment when adding other non-
|
||||
// positional sfx
|
||||
let track = &self
|
||||
.soundtrack
|
||||
.tracks
|
||||
.iter()
|
||||
.find(|track| track.tag == AmbientChannelTag::Wind);
|
||||
|
||||
if let Some(track) = track {
|
||||
self.began_playing = Instant::now();
|
||||
self.next_track_change = track.length;
|
||||
|
||||
audio.play_ambient(AmbientChannelTag::Wind, &track.path, volume_multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_soundtrack_items() -> AmbientCollection {
|
||||
match assets::load_file("voxygen.audio.ambient", &["ron"]) {
|
||||
Ok(file) => match ron::de::from_reader(file) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error parsing music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
AmbientCollection::default()
|
||||
},
|
||||
},
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error reading music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
AmbientCollection::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,9 @@ use crate::audio::{
|
||||
fader::{FadeDirection, Fader},
|
||||
Listener,
|
||||
};
|
||||
use rodio::{Device, Sample, Sink, Source, SpatialSink};
|
||||
use rodio::{OutputStreamHandle, Sample, Sink, Source, SpatialSink};
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
use vek::*;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
@ -53,18 +55,30 @@ pub struct MusicChannel {
|
||||
}
|
||||
|
||||
impl MusicChannel {
|
||||
pub fn new(device: &Device) -> Self {
|
||||
Self {
|
||||
sink: Sink::new(device),
|
||||
tag: MusicChannelTag::TitleMusic,
|
||||
state: ChannelState::Stopped,
|
||||
fader: Fader::default(),
|
||||
pub fn new(stream: &OutputStreamHandle) -> Self {
|
||||
let new_sink = Sink::try_new(stream);
|
||||
match new_sink {
|
||||
Ok(sink) => Self {
|
||||
sink,
|
||||
tag: MusicChannelTag::TitleMusic,
|
||||
state: ChannelState::Stopped,
|
||||
fader: Fader::default(),
|
||||
},
|
||||
Err(_) => {
|
||||
warn!("Failed to create a rodio sink. May not play sounds.");
|
||||
Self {
|
||||
sink: Sink::new_idle().0,
|
||||
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.
|
||||
/// 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,
|
||||
@ -82,6 +96,12 @@ impl MusicChannel {
|
||||
};
|
||||
}
|
||||
|
||||
/// Stop whatever is playing on a given music channel
|
||||
pub fn stop(&mut self, tag: MusicChannelTag) {
|
||||
self.tag = tag;
|
||||
self.sink.stop();
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
@ -134,6 +154,51 @@ impl MusicChannel {
|
||||
}
|
||||
}
|
||||
|
||||
/// AmbientChannelTags are used for non-positional sfx. Currently the only use
|
||||
/// is for wind.
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
|
||||
pub enum AmbientChannelTag {
|
||||
Wind,
|
||||
}
|
||||
/// A AmbientChannel uses a non-positional audio sink designed to play sounds
|
||||
/// which are always heard at the camera's position.
|
||||
pub struct AmbientChannel {
|
||||
tag: AmbientChannelTag,
|
||||
sink: Sink,
|
||||
}
|
||||
|
||||
impl AmbientChannel {
|
||||
pub fn new(stream: &OutputStreamHandle, tag: AmbientChannelTag) -> Self {
|
||||
let new_sink = Sink::try_new(stream);
|
||||
match new_sink {
|
||||
Ok(sink) => Self { sink, tag },
|
||||
Err(_) => {
|
||||
warn!("Failed to create rodio sink. May not play wind sounds.");
|
||||
Self {
|
||||
sink: Sink::new_idle().0,
|
||||
tag,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play<S>(&mut self, source: S)
|
||||
where
|
||||
S: Source + Send + 'static,
|
||||
S::Item: Sample,
|
||||
S::Item: Send,
|
||||
<S as std::iter::Iterator>::Item: std::fmt::Debug,
|
||||
{
|
||||
self.sink.append(source);
|
||||
}
|
||||
|
||||
pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume); }
|
||||
|
||||
pub fn get_volume(&mut self) -> f32 { self.sink.volume() }
|
||||
|
||||
pub fn get_tag(&self) -> AmbientChannelTag { self.tag }
|
||||
}
|
||||
|
||||
/// 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
|
||||
@ -145,9 +210,10 @@ pub struct SfxChannel {
|
||||
}
|
||||
|
||||
impl SfxChannel {
|
||||
pub fn new(device: &Device) -> Self {
|
||||
pub fn new(stream: &OutputStreamHandle) -> Self {
|
||||
Self {
|
||||
sink: SpatialSink::new(device, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]),
|
||||
sink: SpatialSink::try_new(stream, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0])
|
||||
.unwrap(),
|
||||
pos: Vec3::zero(),
|
||||
}
|
||||
}
|
||||
@ -162,6 +228,17 @@ impl SfxChannel {
|
||||
self.sink.append(source);
|
||||
}
|
||||
|
||||
/// Same as SfxChannel::play but with the source passed through
|
||||
/// a low pass filter at 300 Hz
|
||||
pub fn play_with_low_pass_filter<S>(&mut self, source: S)
|
||||
where
|
||||
S: Sized + Send + 'static,
|
||||
S: Source<Item = f32>,
|
||||
{
|
||||
let source = source.low_pass(300);
|
||||
self.sink.append(source);
|
||||
}
|
||||
|
||||
pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume); }
|
||||
|
||||
pub fn is_done(&self) -> bool { self.sink.empty() }
|
||||
|
@ -1,20 +1,21 @@
|
||||
//! Handles audio device detection and playback of sound effects and music
|
||||
|
||||
pub mod ambient;
|
||||
pub mod channel;
|
||||
pub mod fader;
|
||||
pub mod music;
|
||||
pub mod sfx;
|
||||
pub mod soundcache;
|
||||
|
||||
use channel::{MusicChannel, MusicChannelTag, SfxChannel};
|
||||
use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel};
|
||||
use fader::Fader;
|
||||
use sfx::{SfxEvent, SfxTriggerItem};
|
||||
use soundcache::SoundCache;
|
||||
use std::time::Duration;
|
||||
use tracing::warn;
|
||||
use tracing::debug;
|
||||
|
||||
use common::assets;
|
||||
use cpal::traits::DeviceTrait;
|
||||
use rodio::{source::Source, Decoder, Device};
|
||||
use rodio::{source::Source, Decoder, OutputStream, OutputStreamHandle, StreamError};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@ -31,40 +32,56 @@ pub struct Listener {
|
||||
/// Voxygen's [`GlobalState`](../struct.GlobalState.html#structfield.audio) to
|
||||
/// provide access to devices and playback control in-game
|
||||
pub struct AudioFrontend {
|
||||
pub device: String,
|
||||
pub device_list: Vec<String>,
|
||||
audio_device: Option<Device>,
|
||||
// The following is for the disabled device switcher
|
||||
//pub device: String,
|
||||
//pub device_list: Vec<String>,
|
||||
//pub audio_device: Option<Device>,
|
||||
pub stream: Option<rodio::OutputStream>,
|
||||
audio_stream: Option<rodio::OutputStreamHandle>,
|
||||
sound_cache: SoundCache,
|
||||
|
||||
music_channels: Vec<MusicChannel>,
|
||||
ambient_channels: Vec<AmbientChannel>,
|
||||
sfx_channels: Vec<SfxChannel>,
|
||||
|
||||
sfx_volume: f32,
|
||||
music_volume: f32,
|
||||
|
||||
listener: Listener,
|
||||
}
|
||||
|
||||
impl AudioFrontend {
|
||||
/// Construct with given device
|
||||
pub fn new(device: String, max_sfx_channels: usize) -> Self {
|
||||
let audio_device = get_device_raw(&device);
|
||||
pub fn new(/* dev: String, */ max_sfx_channels: usize) -> Self {
|
||||
// Commented out until audio device switcher works
|
||||
//let audio_device = get_device_raw(&dev);
|
||||
|
||||
//let device = match get_default_device() {
|
||||
// Some(d) => d,
|
||||
// None => "".to_string(),
|
||||
//};
|
||||
|
||||
let (stream, audio_stream) = match get_default_stream() {
|
||||
Ok(s) => (Some(s.0), Some(s.1)),
|
||||
Err(_) => (None, None),
|
||||
};
|
||||
|
||||
let mut sfx_channels = Vec::with_capacity(max_sfx_channels);
|
||||
if let Some(audio_device) = &audio_device {
|
||||
sfx_channels.resize_with(max_sfx_channels, || SfxChannel::new(&audio_device));
|
||||
}
|
||||
if let Some(audio_stream) = &audio_stream {
|
||||
sfx_channels.resize_with(max_sfx_channels, || SfxChannel::new(audio_stream));
|
||||
};
|
||||
|
||||
Self {
|
||||
device,
|
||||
device_list: list_devices(),
|
||||
audio_device,
|
||||
// The following is for the disabled device switcher
|
||||
//device,
|
||||
//device_list: list_devices(),
|
||||
//audio_device,
|
||||
stream,
|
||||
audio_stream,
|
||||
sound_cache: SoundCache::default(),
|
||||
music_channels: Vec::new(),
|
||||
sfx_channels,
|
||||
ambient_channels: Vec::new(),
|
||||
sfx_volume: 1.0,
|
||||
music_volume: 1.0,
|
||||
|
||||
listener: Listener::default(),
|
||||
}
|
||||
}
|
||||
@ -72,12 +89,16 @@ impl AudioFrontend {
|
||||
/// Construct in `no-audio` mode for debugging
|
||||
pub fn no_audio() -> Self {
|
||||
Self {
|
||||
device: "none".to_string(),
|
||||
device_list: Vec::new(),
|
||||
audio_device: None,
|
||||
// The following is for the disabled device switcher
|
||||
//device: "".to_string(),
|
||||
//device_list: Vec::new(),
|
||||
//audio_device: None,
|
||||
stream: None,
|
||||
audio_stream: None,
|
||||
sound_cache: SoundCache::default(),
|
||||
music_channels: Vec::new(),
|
||||
sfx_channels: Vec::new(),
|
||||
ambient_channels: Vec::new(),
|
||||
sfx_volume: 1.0,
|
||||
music_volume: 1.0,
|
||||
listener: Listener::default(),
|
||||
@ -93,8 +114,9 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrive an empty sfx channel from the list
|
||||
fn get_sfx_channel(&mut self) -> Option<&mut SfxChannel> {
|
||||
if self.audio_device.is_some() {
|
||||
if self.audio_stream.is_some() {
|
||||
if let Some(channel) = self.sfx_channels.iter_mut().find(|c| c.is_done()) {
|
||||
channel.set_volume(self.sfx_volume);
|
||||
|
||||
@ -114,9 +136,9 @@ impl AudioFrontend {
|
||||
&mut self,
|
||||
next_channel_tag: MusicChannelTag,
|
||||
) -> Option<&mut MusicChannel> {
|
||||
if let Some(audio_device) = &self.audio_device {
|
||||
if let Some(audio_stream) = &self.audio_stream {
|
||||
if self.music_channels.is_empty() {
|
||||
let mut next_music_channel = MusicChannel::new(&audio_device);
|
||||
let mut next_music_channel = MusicChannel::new(audio_stream);
|
||||
next_music_channel.set_volume(self.music_volume);
|
||||
|
||||
self.music_channels.push(next_music_channel);
|
||||
@ -128,7 +150,7 @@ impl AudioFrontend {
|
||||
existing_channel
|
||||
.set_fader(Fader::fade_out(Duration::from_secs(2), self.music_volume));
|
||||
|
||||
let mut next_music_channel = MusicChannel::new(&audio_device);
|
||||
let mut next_music_channel = MusicChannel::new(&audio_stream);
|
||||
|
||||
next_music_channel
|
||||
.set_fader(Fader::fade_in(Duration::from_secs(12), self.music_volume));
|
||||
@ -141,9 +163,74 @@ impl AudioFrontend {
|
||||
self.music_channels.last_mut()
|
||||
}
|
||||
|
||||
/// Function to play sfx from external places. Useful for UI and
|
||||
/// inventory events
|
||||
pub fn emit_sfx_item(&mut self, trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>) {
|
||||
if let Some((event, item)) = trigger_item {
|
||||
let sfx_file = match item.files.len() {
|
||||
0 => {
|
||||
debug!("Sfx event {:?} is missing audio file.", event);
|
||||
"voxygen.audio.sfx.placeholder"
|
||||
},
|
||||
1 => item
|
||||
.files
|
||||
.last()
|
||||
.expect("Failed to determine sound file for this trigger item."),
|
||||
_ => {
|
||||
// If more than one file is listed, choose one at random
|
||||
let rand_step = rand::random::<usize>() % item.files.len();
|
||||
&item.files[rand_step]
|
||||
},
|
||||
};
|
||||
|
||||
self.play_sfx(sfx_file, self.listener.pos, None);
|
||||
} else {
|
||||
debug!("Missing sfx trigger config for external sfx event.",);
|
||||
}
|
||||
}
|
||||
|
||||
/// Play an sfx file given the position, SfxEvent, and whether it is
|
||||
/// underwater or not
|
||||
pub fn emit_sfx(
|
||||
&mut self,
|
||||
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||
position: Vec3<f32>,
|
||||
volume: Option<f32>,
|
||||
underwater: bool,
|
||||
) {
|
||||
if let Some((event, item)) = trigger_item {
|
||||
let sfx_file = match item.files.len() {
|
||||
0 => {
|
||||
debug!("Sfx event {:?} is missing audio file.", event);
|
||||
"voxygen.audio.sfx.placeholder"
|
||||
},
|
||||
1 => item
|
||||
.files
|
||||
.last()
|
||||
.expect("Failed to determine sound file for this trigger item."),
|
||||
_ => {
|
||||
// If more than one file is listed, choose one at random
|
||||
let rand_step = rand::random::<usize>() % item.files.len();
|
||||
&item.files[rand_step]
|
||||
},
|
||||
};
|
||||
|
||||
if underwater {
|
||||
self.play_underwater_sfx(sfx_file, position, volume);
|
||||
} else {
|
||||
self.play_sfx(sfx_file, position, volume);
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Missing sfx trigger config for sfx event at position: {:?}",
|
||||
position
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Play (once) an sfx file by file path at the give position and volume
|
||||
pub fn play_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
||||
if self.audio_device.is_some() {
|
||||
if self.audio_stream.is_some() {
|
||||
let sound = self
|
||||
.sound_cache
|
||||
.load_sound(sound)
|
||||
@ -158,15 +245,119 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
/// Play (once) an sfx file by file path at the give position and volume
|
||||
/// but with the sound passed through a low pass filter to simulate
|
||||
/// being underwater
|
||||
pub fn play_underwater_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
||||
if self.audio_stream.is_some() {
|
||||
let sound = self
|
||||
.sound_cache
|
||||
.load_sound(sound)
|
||||
.amplify(vol.unwrap_or(1.0));
|
||||
|
||||
channel.play(sound, channel_tag);
|
||||
let listener = self.listener.clone();
|
||||
if let Some(channel) = self.get_sfx_channel() {
|
||||
channel.set_pos(pos);
|
||||
channel.update(&listener);
|
||||
channel.play_with_low_pass_filter(sound.convert_samples());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn play_ambient(
|
||||
&mut self,
|
||||
channel_tag: AmbientChannelTag,
|
||||
sound: &str,
|
||||
volume_multiplier: f32,
|
||||
) {
|
||||
if self.audio_stream.is_some() {
|
||||
if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) {
|
||||
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
|
||||
let sound = Decoder::new(file).expect("Failed to decode sound");
|
||||
|
||||
channel.play(sound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ambient_channel(
|
||||
&mut self,
|
||||
channel_tag: AmbientChannelTag,
|
||||
volume_multiplier: f32,
|
||||
) -> Option<&mut AmbientChannel> {
|
||||
if let Some(audio_stream) = &self.audio_stream {
|
||||
if self.ambient_channels.is_empty() {
|
||||
let mut ambient_channel = AmbientChannel::new(audio_stream, channel_tag);
|
||||
ambient_channel.set_volume(self.sfx_volume * volume_multiplier);
|
||||
self.ambient_channels.push(ambient_channel);
|
||||
} else {
|
||||
for channel in self.ambient_channels.iter_mut() {
|
||||
if channel.get_tag() == channel_tag {
|
||||
channel.set_volume(self.sfx_volume * volume_multiplier);
|
||||
return Some(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn set_ambient_volume(&mut self, volume_multiplier: f32) {
|
||||
if self.audio_stream.is_some() {
|
||||
if let Some(channel) = self.ambient_channels.iter_mut().last() {
|
||||
channel.set_volume(self.sfx_volume * volume_multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ambient_volume(&mut self) -> f32 {
|
||||
if self.audio_stream.is_some() {
|
||||
if let Some(channel) = self.ambient_channels.iter_mut().last() {
|
||||
channel.get_volume() / self.sfx_volume
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
|
||||
if self.music_enabled() {
|
||||
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.play(sound, channel_tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* These functions are saved for if we want music playback control at some
|
||||
* point. They are not used currently but may be useful for later work.
|
||||
*
|
||||
fn fade_out_music(&mut self, channel_tag: MusicChannelTag) {
|
||||
let music_volume = self.music_volume;
|
||||
if let Some(channel) = self.get_music_channel(channel_tag) {
|
||||
channel.set_fader(Fader::fade_out(Duration::from_secs(5), music_volume));
|
||||
}
|
||||
}
|
||||
|
||||
fn fade_in_music(&mut self, channel_tag: MusicChannelTag) {
|
||||
let music_volume = self.music_volume;
|
||||
if let Some(channel) = self.get_music_channel(channel_tag) {
|
||||
channel.set_fader(Fader::fade_in(Duration::from_secs(5), music_volume));
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_music(&mut self, channel_tag: MusicChannelTag) {
|
||||
if let Some(channel) = self.get_music_channel(channel_tag) {
|
||||
channel.stop(channel_tag);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn set_listener_pos(&mut self, pos: Vec3<f32>, ori: Vec3<f32>) {
|
||||
self.listener.pos = pos;
|
||||
self.listener.ori = ori.normalized();
|
||||
@ -193,12 +384,6 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_exploration_music(&mut self, item: &str) {
|
||||
if self.music_enabled() {
|
||||
self.play_music(item, MusicChannelTag::Exploration)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sfx_volume(&self) -> f32 { self.sfx_volume }
|
||||
|
||||
pub fn get_music_volume(&self) -> f32 { self.music_volume }
|
||||
@ -223,47 +408,58 @@ impl AudioFrontend {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how badly this will break things when it is called
|
||||
pub fn set_device(&mut self, name: String) {
|
||||
self.device = name.clone();
|
||||
self.audio_device = get_device_raw(&name);
|
||||
}
|
||||
// The following is for the disabled device switcher
|
||||
//// TODO: figure out how badly this will break things when it is called
|
||||
//pub fn set_device(&mut self, name: String) {
|
||||
// self.device = name.clone();
|
||||
// self.audio_device = get_device_raw(&name);
|
||||
//}
|
||||
}
|
||||
|
||||
/// Returns the default audio device.
|
||||
/// Does not return rodio Device struct in case our audio backend changes.
|
||||
pub fn get_default_device() -> Option<String> {
|
||||
match rodio::default_output_device() {
|
||||
Some(x) => Some(x.name().ok()?),
|
||||
None => None,
|
||||
}
|
||||
// The following is for the disabled device switcher
|
||||
///// Returns the default audio device.
|
||||
///// Does not return rodio Device struct in case our audio backend changes.
|
||||
//pub fn get_default_device() -> Option<String> {
|
||||
// match cpal::default_host().default_output_device() {
|
||||
// Some(x) => Some(x.name().ok()?),
|
||||
// None => None,
|
||||
// }
|
||||
//}
|
||||
|
||||
/// Returns the default stream
|
||||
pub fn get_default_stream() -> Result<(OutputStream, OutputStreamHandle), StreamError> {
|
||||
rodio::OutputStream::try_default()
|
||||
}
|
||||
|
||||
/// Returns a vec of the audio devices available.
|
||||
/// Does not return rodio Device struct in case our audio backend changes.
|
||||
pub fn list_devices() -> Vec<String> {
|
||||
list_devices_raw()
|
||||
.iter()
|
||||
.map(|x| x.name().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns vec of devices
|
||||
fn list_devices_raw() -> Vec<Device> {
|
||||
match rodio::output_devices() {
|
||||
Ok(devices) => {
|
||||
// Filter out any devices that the name isn't available for
|
||||
devices.filter(|d| d.name().is_ok()).collect()
|
||||
},
|
||||
Err(_) => {
|
||||
warn!("Failed to enumerate audio output devices, audio will not be available");
|
||||
Vec::new()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_device_raw(device: &str) -> Option<Device> {
|
||||
list_devices_raw()
|
||||
.into_iter()
|
||||
.find(|d| d.name().unwrap() == device)
|
||||
}
|
||||
// The following is for the disabled device switcher
|
||||
///// Returns a stream on the specified device
|
||||
//pub fn get_stream(
|
||||
// device: &rodio::Device,
|
||||
//) -> Result<(OutputStream, OutputStreamHandle), StreamError> {
|
||||
// rodio::OutputStream::try_from_device(device)
|
||||
//}
|
||||
//
|
||||
//fn list_devices_raw() -> Vec<cpal::Device> {
|
||||
// match cpal::default_host().devices() {
|
||||
// Ok(devices) => devices.filter(|d| d.name().is_ok()).collect(),
|
||||
// Err(_) => {
|
||||
// warn!("Failed to enumerate audio output devices, audio will not be
|
||||
// available"); Vec::new()
|
||||
// },
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///// Returns a vec of the audio devices available.
|
||||
///// Does not return rodio Device struct in case our audio backend changes.
|
||||
//fn list_devices() -> Vec<String> {
|
||||
// list_devices_raw()
|
||||
// .iter()
|
||||
// .map(|x| x.name().unwrap())
|
||||
// .collect()
|
||||
//}
|
||||
//
|
||||
//fn get_device_raw(device: &str) -> Option<Device> {
|
||||
// list_devices_raw()
|
||||
// .into_iter()
|
||||
// .find(|d| d.name().unwrap() == device)
|
||||
//}
|
||||
|
@ -26,6 +26,11 @@
|
||||
//! path: "voxygen.audio.soundtrack.sleepy",
|
||||
//! length: 400.0,
|
||||
//! timing: Some(Night),
|
||||
//! biomes: [
|
||||
//! (Forest, 1),
|
||||
//! (Grassland, 2),
|
||||
//! ],
|
||||
//! site: None,
|
||||
//! artist: "Elvis",
|
||||
//! ),
|
||||
//! ```
|
||||
@ -37,30 +42,44 @@
|
||||
//! tracks
|
||||
//! - If you are not the author of the track, ensure that the song's licensing
|
||||
//! permits usage of the track for non-commercial use
|
||||
use crate::audio::AudioFrontend;
|
||||
use common::{assets, state::State};
|
||||
use rand::{seq::IteratorRandom, thread_rng};
|
||||
use crate::audio::{AudioFrontend, MusicChannelTag};
|
||||
use client::Client;
|
||||
use common::{
|
||||
assets,
|
||||
state::State,
|
||||
terrain::{BiomeKind, SitesKind},
|
||||
};
|
||||
use rand::{prelude::SliceRandom, thread_rng, Rng};
|
||||
use serde::Deserialize;
|
||||
use std::time::Instant;
|
||||
use tracing::warn;
|
||||
|
||||
// TODO These should eventually not be constants if we have seasons
|
||||
const DAY_START_SECONDS: u32 = 28800; // 8:00
|
||||
const DAY_END_SECONDS: u32 = 70200; // 19:30
|
||||
|
||||
/// Collection of all the tracks
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct SoundtrackCollection {
|
||||
/// List of tracks
|
||||
tracks: Vec<SoundtrackItem>,
|
||||
}
|
||||
|
||||
/// Configuration for a single music track in the soundtrack
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SoundtrackItem {
|
||||
/// Song title
|
||||
title: String,
|
||||
/// File path to asset
|
||||
path: String,
|
||||
/// Length of the track in seconds
|
||||
length: f64,
|
||||
length: f32,
|
||||
/// Whether this track should play during day or night
|
||||
timing: Option<DayPeriod>,
|
||||
/// What biomes this track should play in with chance of play
|
||||
biomes: Vec<(BiomeKind, u8)>,
|
||||
/// Whether this track should play in a specific site
|
||||
site: Option<SitesKind>,
|
||||
}
|
||||
|
||||
/// Allows control over when a track should play based on in-game time of day
|
||||
@ -72,19 +91,30 @@ enum DayPeriod {
|
||||
Night,
|
||||
}
|
||||
|
||||
/// Determines whether the sound is stopped, playing, or fading
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
enum PlayState {
|
||||
Playing,
|
||||
Stopped,
|
||||
FadingOut,
|
||||
FadingIn,
|
||||
}
|
||||
|
||||
/// Provides methods to control music playback
|
||||
pub struct MusicMgr {
|
||||
/// Collection of all the tracks
|
||||
soundtrack: SoundtrackCollection,
|
||||
/// Instant at which the current track began playing
|
||||
began_playing: Instant,
|
||||
next_track_change: f64,
|
||||
/// Time until the next track should be played
|
||||
next_track_change: f32,
|
||||
/// The title of the last track played. Used to prevent a track
|
||||
/// being played twice in a row
|
||||
last_track: String,
|
||||
}
|
||||
|
||||
impl MusicMgr {
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self {
|
||||
impl Default for MusicMgr {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
soundtrack: Self::load_soundtrack_items(),
|
||||
began_playing: Instant::now(),
|
||||
@ -92,26 +122,43 @@ impl MusicMgr {
|
||||
last_track: String::from("None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MusicMgr {
|
||||
/// Checks whether the previous track has completed. If so, sends a
|
||||
/// request to play the next (random) track
|
||||
pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State) {
|
||||
pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
|
||||
//if let Some(current_chunk) = client.current_chunk() {
|
||||
//println!("biome: {:?}", current_chunk.meta().biome());
|
||||
//println!("chaos: {}", current_chunk.meta().chaos());
|
||||
//println!("alt: {}", current_chunk.meta().alt());
|
||||
//println!("tree_density: {}",
|
||||
// current_chunk.meta().tree_density());
|
||||
//if let Some(position) = client.current::<comp::Pos>() {
|
||||
// player_alt = position.0.z;
|
||||
//}
|
||||
|
||||
if audio.music_enabled()
|
||||
&& !self.soundtrack.tracks.is_empty()
|
||||
&& self.began_playing.elapsed().as_secs_f64() > self.next_track_change
|
||||
&& self.began_playing.elapsed().as_secs_f32() > self.next_track_change
|
||||
{
|
||||
self.play_random_track(audio, state);
|
||||
self.play_random_track(audio, state, client);
|
||||
}
|
||||
}
|
||||
|
||||
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State) {
|
||||
const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0;
|
||||
fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State, client: &Client) {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
// Adds a bit of randomness between plays
|
||||
let silence_between_tracks_seconds: f32 = rng.gen_range(45.0, 120.0);
|
||||
|
||||
let game_time = (state.get_time_of_day() as u64 % 86400) as u32;
|
||||
let current_period_of_day = Self::get_current_day_period(game_time);
|
||||
let mut rng = thread_rng();
|
||||
let current_biome = client.current_biome();
|
||||
let current_site = client.current_site();
|
||||
|
||||
let maybe_track = self
|
||||
// Filters out tracks not matching the timing, site, and biome
|
||||
let maybe_tracks = self
|
||||
.soundtrack
|
||||
.tracks
|
||||
.iter()
|
||||
@ -121,15 +168,52 @@ impl MusicMgr {
|
||||
Some(period_of_day) => period_of_day == ¤t_period_of_day,
|
||||
None => true,
|
||||
}
|
||||
&& match &track.site {
|
||||
Some(site) => site == ¤t_site,
|
||||
None => true,
|
||||
}
|
||||
})
|
||||
.choose(&mut rng);
|
||||
.filter(|track| {
|
||||
let mut result = false;
|
||||
if !track.biomes.is_empty() {
|
||||
for biome in track.biomes.iter() {
|
||||
if biome.0 == current_biome {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
result
|
||||
})
|
||||
.collect::<Vec<&SoundtrackItem>>();
|
||||
|
||||
if let Some(track) = maybe_track {
|
||||
// Randomly selects a track from the remaining tracks weighted based
|
||||
// on the biome
|
||||
let new_maybe_track = maybe_tracks.choose_weighted(&mut rng, |track| {
|
||||
let mut chance = 0;
|
||||
if !track.biomes.is_empty() {
|
||||
for biome in track.biomes.iter() {
|
||||
if biome.0 == current_biome {
|
||||
chance = biome.1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no biome is listed, the song is still added to the
|
||||
// rotation to allow for site specific songs to play
|
||||
// in any biome
|
||||
chance = 1;
|
||||
}
|
||||
chance
|
||||
});
|
||||
|
||||
if let Ok(track) = new_maybe_track {
|
||||
//println!("Now playing {:?}", track.title);
|
||||
self.last_track = String::from(&track.title);
|
||||
self.began_playing = Instant::now();
|
||||
self.next_track_change = track.length + SILENCE_BETWEEN_TRACKS_SECONDS;
|
||||
self.next_track_change = track.length + silence_between_tracks_seconds;
|
||||
|
||||
audio.play_exploration_music(&track.path);
|
||||
audio.play_music(&track.path, MusicChannelTag::Exploration);
|
||||
}
|
||||
}
|
||||
|
||||
|
240
voxygen/src/audio/sfx/event_mapper/block/mod.rs
Normal file
240
voxygen/src/audio/sfx/event_mapper/block/mod.rs
Normal file
@ -0,0 +1,240 @@
|
||||
/// EventMapper::Block watches the sound emitting blocks within
|
||||
/// chunk range of the player and emits ambient sfx
|
||||
use crate::{
|
||||
audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||
scene::{terrain::BlocksOfInterest, Camera, Terrain},
|
||||
AudioFrontend,
|
||||
};
|
||||
|
||||
use super::EventMapper;
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp::Pos,
|
||||
spiral::Spiral2d,
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
vol::{ReadVol, RectRasterableVol},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::time::Instant;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct PreviousBlockState {
|
||||
event: SfxEvent,
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
impl Default for PreviousBlockState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockEventMapper {
|
||||
history: HashMap<Vec3<i32>, PreviousBlockState>,
|
||||
}
|
||||
|
||||
impl EventMapper for BlockEventMapper {
|
||||
fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
terrain: &Terrain<TerrainChunk>,
|
||||
client: &Client,
|
||||
) {
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
|
||||
// Get the player position and chunk
|
||||
let player_pos = state
|
||||
.read_component_copied::<Pos>(player_entity)
|
||||
.unwrap_or_default();
|
||||
let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
(e.floor() as i32).div_euclid(sz as i32)
|
||||
});
|
||||
|
||||
// For determining if underground
|
||||
let terrain_alt = match client.current_chunk() {
|
||||
Some(chunk) => chunk.meta().alt(),
|
||||
None => 0.0,
|
||||
};
|
||||
|
||||
struct BlockSounds<'a> {
|
||||
// The function to select the blocks of interest that we should emit from
|
||||
blocks: fn(&'a BlocksOfInterest) -> &'a [Vec3<i32>],
|
||||
// The range, in chunks, that the particles should be generated in from the player
|
||||
range: usize,
|
||||
// The sound of the generated particle
|
||||
sfx: SfxEvent,
|
||||
// The volume of the sfx
|
||||
volume: f32,
|
||||
// Condition that must be true to play
|
||||
cond: fn(&State) -> bool,
|
||||
}
|
||||
|
||||
let sounds: &[BlockSounds] = &[
|
||||
BlockSounds {
|
||||
blocks: |boi| &boi.leaves,
|
||||
range: 1,
|
||||
sfx: SfxEvent::Birdcall,
|
||||
volume: 1.0,
|
||||
cond: |st| st.get_day_period().is_light(),
|
||||
},
|
||||
BlockSounds {
|
||||
blocks: |boi| &boi.leaves,
|
||||
range: 1,
|
||||
sfx: SfxEvent::Owl,
|
||||
volume: 1.0,
|
||||
cond: |st| st.get_day_period().is_dark(),
|
||||
},
|
||||
BlockSounds {
|
||||
blocks: |boi| &boi.river,
|
||||
range: 1,
|
||||
sfx: SfxEvent::RunningWater,
|
||||
volume: 1.0,
|
||||
cond: |_| true,
|
||||
},
|
||||
//BlockSounds {
|
||||
// blocks: |boi| &boi.embers,
|
||||
// range: 1,
|
||||
// sfx: SfxEvent::Embers,
|
||||
// volume: 0.15,
|
||||
// //volume: 0.05,
|
||||
// cond: |_| true,
|
||||
// //cond: |st| st.get_day_period().is_dark(),
|
||||
//},
|
||||
BlockSounds {
|
||||
blocks: |boi| &boi.reeds,
|
||||
range: 1,
|
||||
sfx: SfxEvent::Frog,
|
||||
volume: 0.8,
|
||||
cond: |st| st.get_day_period().is_dark(),
|
||||
},
|
||||
//BlockSounds {
|
||||
// blocks: |boi| &boi.flowers,
|
||||
// range: 4,
|
||||
// sfx: SfxEvent::LevelUp,
|
||||
// volume: 1.0,
|
||||
// cond: |st| st.get_day_period().is_dark(),
|
||||
//},
|
||||
BlockSounds {
|
||||
blocks: |boi| &boi.grass,
|
||||
range: 1,
|
||||
sfx: SfxEvent::Cricket,
|
||||
volume: 0.5,
|
||||
cond: |st| st.get_day_period().is_dark(),
|
||||
},
|
||||
BlockSounds {
|
||||
blocks: |boi| &boi.beehives,
|
||||
range: 1,
|
||||
sfx: SfxEvent::Bees,
|
||||
volume: 1.0,
|
||||
cond: |st| st.get_day_period().is_light(),
|
||||
},
|
||||
];
|
||||
|
||||
// Iterate through each kind of block of interest
|
||||
for sounds in sounds.iter() {
|
||||
// If the timing condition is false, continue
|
||||
// or if the player is far enough underground, continue
|
||||
// TODO Address bird hack properly. See TODO on line 171
|
||||
if !(sounds.cond)(state)
|
||||
|| player_pos.0.z < (terrain_alt - 30.0)
|
||||
|| ((sounds.sfx == SfxEvent::Birdcall || sounds.sfx == SfxEvent::Owl)
|
||||
&& thread_rng().gen_bool(0.995))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// For chunks surrounding the player position
|
||||
for offset in Spiral2d::new().take((sounds.range * 2 + 1).pow(2)) {
|
||||
let chunk_pos = player_chunk + offset;
|
||||
|
||||
// Get all the blocks of interest in this chunk
|
||||
terrain.get(chunk_pos).map(|chunk_data| {
|
||||
// Get the positions of the blocks of type sounds
|
||||
let blocks = (sounds.blocks)(&chunk_data.blocks_of_interest);
|
||||
|
||||
let absolute_pos: Vec3<i32> =
|
||||
Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
||||
|
||||
// Iterate through each individual block
|
||||
for block in blocks {
|
||||
// TODO Address this hack properly, potentially by making a new
|
||||
// block of interest type which picks fewer leaf blocks
|
||||
// Hack to reduce the number of bird sounds (too many leaf blocks)
|
||||
if (sounds.sfx == SfxEvent::Birdcall || sounds.sfx == SfxEvent::Owl)
|
||||
&& thread_rng().gen_bool(0.999)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let block_pos: Vec3<i32> = absolute_pos + block;
|
||||
let internal_state = self.history.entry(block_pos).or_default();
|
||||
|
||||
let block_pos = block_pos.map(|x| x as f32);
|
||||
|
||||
if Self::should_emit(internal_state, triggers.get_key_value(&sounds.sfx)) {
|
||||
// If the camera is within SFX distance
|
||||
if (block_pos.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR {
|
||||
let underwater = state
|
||||
.terrain()
|
||||
.get(cam_pos.map(|e| e.floor() as i32))
|
||||
.map(|b| b.is_liquid())
|
||||
.unwrap_or(false);
|
||||
|
||||
let sfx_trigger_item = triggers.get_key_value(&sounds.sfx);
|
||||
audio.emit_sfx(
|
||||
sfx_trigger_item,
|
||||
block_pos,
|
||||
Some(sounds.volume),
|
||||
underwater,
|
||||
);
|
||||
}
|
||||
internal_state.time = Instant::now();
|
||||
internal_state.event = sounds.sfx.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockEventMapper {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
history: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that:
|
||||
/// 1. An sfx.ron entry exists for an SFX event
|
||||
/// 2. The sfx has not been played since it's timeout threshold has elapsed,
|
||||
/// which prevents firing every tick
|
||||
/// Note that with so many blocks to choose from and different blocks being
|
||||
/// selected each time, this is not perfect, but does reduce the number of
|
||||
/// plays from blocks that have already emitted sfx and are stored in the
|
||||
/// BlockEventMapper history.
|
||||
fn should_emit(
|
||||
previous_state: &PreviousBlockState,
|
||||
sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||
) -> bool {
|
||||
if let Some((event, item)) = sfx_trigger_item {
|
||||
if &previous_state.event == event {
|
||||
previous_state.time.elapsed().as_secs_f32() >= item.threshold
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
128
voxygen/src/audio/sfx/event_mapper/campfire/mod.rs
Normal file
128
voxygen/src/audio/sfx/event_mapper/campfire/mod.rs
Normal file
@ -0,0 +1,128 @@
|
||||
/// EventMapper::Campfire maps sfx to campfires
|
||||
use crate::{
|
||||
audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||
scene::{Camera, Terrain},
|
||||
AudioFrontend,
|
||||
};
|
||||
|
||||
use super::EventMapper;
|
||||
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp::{object, Body, Pos},
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PreviousEntityState {
|
||||
event: SfxEvent,
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
impl Default for PreviousEntityState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CampfireEventMapper {
|
||||
event_history: HashMap<EcsEntity, PreviousEntityState>,
|
||||
}
|
||||
|
||||
impl EventMapper for CampfireEventMapper {
|
||||
fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
_terrain: &Terrain<TerrainChunk>,
|
||||
_client: &Client,
|
||||
) {
|
||||
let ecs = state.ecs();
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
for (entity, body, pos) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, e_pos)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
|
||||
{
|
||||
if let Body::Object(object::Body::CampfireLit) = body {
|
||||
let internal_state = self.event_history.entry(entity).or_default();
|
||||
|
||||
let mapped_event = SfxEvent::Campfire;
|
||||
|
||||
// Check for SFX config entry for this movement
|
||||
if Self::should_emit(internal_state, triggers.get_key_value(&mapped_event)) {
|
||||
let underwater = state
|
||||
.terrain()
|
||||
.get(cam_pos.map(|e| e.floor() as i32))
|
||||
.map(|b| b.is_liquid())
|
||||
.unwrap_or(false);
|
||||
let sfx_trigger_item = triggers.get_key_value(&mapped_event);
|
||||
audio.emit_sfx(sfx_trigger_item, pos.0, None, underwater);
|
||||
internal_state.time = Instant::now();
|
||||
}
|
||||
|
||||
// update state to determine the next event. We only record the time (above) if
|
||||
// it was dispatched
|
||||
internal_state.event = mapped_event;
|
||||
}
|
||||
}
|
||||
self.cleanup(player_entity);
|
||||
}
|
||||
}
|
||||
|
||||
impl CampfireEventMapper {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
event_history: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// As the player explores the world, we track the last event of the nearby
|
||||
/// entities to determine the correct SFX item to play next based on
|
||||
/// their activity. `cleanup` will remove entities from event tracking if
|
||||
/// 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 = 10;
|
||||
|
||||
let now = Instant::now();
|
||||
self.event_history.retain(|entity, event| {
|
||||
now.duration_since(event.time) < Duration::from_secs(TRACKING_TIMEOUT)
|
||||
|| entity.id() == player.id()
|
||||
});
|
||||
}
|
||||
|
||||
/// Ensures that:
|
||||
/// 1. An sfx.ron entry exists for an SFX event
|
||||
/// 2. The sfx has not been played since it's timeout threshold has elapsed,
|
||||
/// which prevents firing every tick
|
||||
fn should_emit(
|
||||
previous_state: &PreviousEntityState,
|
||||
sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||
) -> bool {
|
||||
if let Some((event, item)) = sfx_trigger_item {
|
||||
if &previous_state.event == event {
|
||||
previous_state.time.elapsed().as_secs_f32() >= item.threshold
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,19 @@
|
||||
/// EventMapper::Combat watches the combat states of surrounding entities' and
|
||||
/// emits sfx related to weapons and attacks/abilities
|
||||
use crate::{
|
||||
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||
scene::Camera,
|
||||
audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||
scene::{Camera, Terrain},
|
||||
AudioFrontend,
|
||||
};
|
||||
|
||||
use super::EventMapper;
|
||||
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp::{item::ItemKind, CharacterAbilityType, CharacterState, Loadout, Pos},
|
||||
event::EventBus,
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||
@ -40,16 +43,16 @@ pub struct CombatEventMapper {
|
||||
impl EventMapper for CombatEventMapper {
|
||||
fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
_terrain: &Terrain<TerrainChunk>,
|
||||
_client: &Client,
|
||||
) {
|
||||
let ecs = state.ecs();
|
||||
|
||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||
let mut sfx_emitter = sfx_event_bus.emitter();
|
||||
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
|
||||
@ -63,21 +66,27 @@ impl EventMapper for CombatEventMapper {
|
||||
.filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
|
||||
{
|
||||
if let Some(character) = character {
|
||||
let state = self.event_history.entry(entity).or_default();
|
||||
let sfx_state = self.event_history.entry(entity).or_default();
|
||||
|
||||
let mapped_event = Self::map_event(character, state, loadout);
|
||||
let mapped_event = Self::map_event(character, sfx_state, loadout);
|
||||
|
||||
// Check for SFX config entry for this movement
|
||||
if Self::should_emit(state, triggers.get_key_value(&mapped_event)) {
|
||||
sfx_emitter.emit(SfxEventItem::new(mapped_event.clone(), Some(pos.0), None));
|
||||
if Self::should_emit(sfx_state, triggers.get_key_value(&mapped_event)) {
|
||||
let underwater = state
|
||||
.terrain()
|
||||
.get(cam_pos.map(|e| e.floor() as i32))
|
||||
.map(|b| b.is_liquid())
|
||||
.unwrap_or(false);
|
||||
|
||||
state.time = Instant::now();
|
||||
let sfx_trigger_item = triggers.get_key_value(&mapped_event);
|
||||
audio.emit_sfx(sfx_trigger_item, pos.0, None, underwater);
|
||||
sfx_state.time = Instant::now();
|
||||
}
|
||||
|
||||
// update state to determine the next event. We only record the time (above) if
|
||||
// it was dispatched
|
||||
state.event = mapped_event;
|
||||
state.weapon_drawn = Self::weapon_drawn(character);
|
||||
sfx_state.event = mapped_event;
|
||||
sfx_state.weapon_drawn = Self::weapon_drawn(character);
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +126,7 @@ impl CombatEventMapper {
|
||||
) -> bool {
|
||||
if let Some((event, item)) = sfx_trigger_item {
|
||||
if &previous_state.event == event {
|
||||
previous_state.time.elapsed().as_secs_f64() >= item.threshold
|
||||
previous_state.time.elapsed().as_secs_f32() >= item.threshold
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -1,23 +1,32 @@
|
||||
mod block;
|
||||
mod campfire;
|
||||
mod combat;
|
||||
mod movement;
|
||||
mod progression;
|
||||
|
||||
use common::state::State;
|
||||
use client::Client;
|
||||
use common::{state::State, terrain::TerrainChunk};
|
||||
|
||||
use block::BlockEventMapper;
|
||||
use campfire::CampfireEventMapper;
|
||||
use combat::CombatEventMapper;
|
||||
use movement::MovementEventMapper;
|
||||
use progression::ProgressionEventMapper;
|
||||
|
||||
use super::SfxTriggers;
|
||||
use crate::scene::Camera;
|
||||
use crate::{
|
||||
scene::{Camera, Terrain},
|
||||
AudioFrontend,
|
||||
};
|
||||
|
||||
trait EventMapper {
|
||||
fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
terrain: &Terrain<TerrainChunk>,
|
||||
client: &Client,
|
||||
);
|
||||
}
|
||||
|
||||
@ -31,20 +40,32 @@ impl SfxEventMapper {
|
||||
mappers: vec![
|
||||
Box::new(CombatEventMapper::new()),
|
||||
Box::new(MovementEventMapper::new()),
|
||||
Box::new(ProgressionEventMapper::new()),
|
||||
Box::new(BlockEventMapper::new()),
|
||||
Box::new(CampfireEventMapper::new()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
terrain: &Terrain<TerrainChunk>,
|
||||
client: &Client,
|
||||
) {
|
||||
for mapper in &mut self.mappers {
|
||||
mapper.maintain(state, player_entity, camera, triggers);
|
||||
mapper.maintain(
|
||||
audio,
|
||||
state,
|
||||
player_entity,
|
||||
camera,
|
||||
triggers,
|
||||
terrain,
|
||||
client,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,16 @@
|
||||
/// proportionate to the extity's size
|
||||
use super::EventMapper;
|
||||
use crate::{
|
||||
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||
scene::Camera,
|
||||
audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||
scene::{Camera, Terrain},
|
||||
AudioFrontend,
|
||||
};
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp::{Body, CharacterState, PhysicsState, Pos, Vel},
|
||||
event::EventBus,
|
||||
state::State,
|
||||
terrain::{BlockKind, TerrainChunk},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||
@ -21,6 +24,7 @@ struct PreviousEntityState {
|
||||
event: SfxEvent,
|
||||
time: Instant,
|
||||
on_ground: bool,
|
||||
in_water: bool,
|
||||
}
|
||||
|
||||
impl Default for PreviousEntityState {
|
||||
@ -29,6 +33,7 @@ impl Default for PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,16 +45,16 @@ pub struct MovementEventMapper {
|
||||
impl EventMapper for MovementEventMapper {
|
||||
fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
_terrain: &Terrain<TerrainChunk>,
|
||||
_client: &Client,
|
||||
) {
|
||||
let ecs = state.ecs();
|
||||
|
||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||
let mut sfx_emitter = sfx_event_bus.emitter();
|
||||
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
|
||||
@ -65,34 +70,59 @@ impl EventMapper for MovementEventMapper {
|
||||
.filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
|
||||
{
|
||||
if let Some(character) = character {
|
||||
let state = self.event_history.entry(entity).or_default();
|
||||
let internal_state = self.event_history.entry(entity).or_default();
|
||||
|
||||
// Get the underfoot block
|
||||
let block_position = Vec3::new(pos.0.x, pos.0.y, pos.0.z - 1.0).map(|x| x as i32);
|
||||
let underfoot_block_kind = match state.get_block(block_position) {
|
||||
Some(block) => block.kind(),
|
||||
None => BlockKind::Air,
|
||||
};
|
||||
|
||||
let mapped_event = match body {
|
||||
Body::Humanoid(_) => Self::map_movement_event(character, physics, state, vel.0),
|
||||
Body::QuadrupedMedium(_)
|
||||
| Body::QuadrupedSmall(_)
|
||||
| Body::QuadrupedLow(_)
|
||||
| Body::BirdMedium(_)
|
||||
| Body::BirdSmall(_)
|
||||
| Body::BipedLarge(_) => Self::map_non_humanoid_movement_event(physics, vel.0),
|
||||
Body::Humanoid(_) => Self::map_movement_event(
|
||||
character,
|
||||
physics,
|
||||
internal_state,
|
||||
vel.0,
|
||||
underfoot_block_kind,
|
||||
),
|
||||
Body::QuadrupedMedium(_) | Body::QuadrupedSmall(_) | Body::QuadrupedLow(_) => {
|
||||
Self::map_quadruped_movement_event(physics, vel.0, underfoot_block_kind)
|
||||
},
|
||||
Body::BirdMedium(_) | Body::BirdSmall(_) | Body::BipedLarge(_) => {
|
||||
Self::map_non_humanoid_movement_event(physics, vel.0, underfoot_block_kind)
|
||||
},
|
||||
_ => SfxEvent::Idle, // Ignore fish, etc...
|
||||
};
|
||||
|
||||
// Check for SFX config entry for this movement
|
||||
if Self::should_emit(state, triggers.get_key_value(&mapped_event)) {
|
||||
sfx_emitter.emit(SfxEventItem::new(
|
||||
mapped_event.clone(),
|
||||
Some(pos.0),
|
||||
Some(Self::get_volume_for_body_type(body)),
|
||||
));
|
||||
if Self::should_emit(internal_state, triggers.get_key_value(&mapped_event)) {
|
||||
let underwater = state
|
||||
.terrain()
|
||||
.get(cam_pos.map(|e| e.floor() as i32))
|
||||
.map(|b| b.is_liquid())
|
||||
.unwrap_or(false);
|
||||
|
||||
state.time = Instant::now();
|
||||
let sfx_trigger_item = triggers.get_key_value(&mapped_event);
|
||||
audio.emit_sfx(
|
||||
sfx_trigger_item,
|
||||
pos.0,
|
||||
Some(Self::get_volume_for_body_type(body)),
|
||||
underwater,
|
||||
);
|
||||
internal_state.time = Instant::now();
|
||||
}
|
||||
|
||||
// update state to determine the next event. We only record the time (above) if
|
||||
// it was dispatched
|
||||
state.event = mapped_event;
|
||||
state.on_ground = physics.on_ground;
|
||||
internal_state.event = mapped_event;
|
||||
internal_state.on_ground = physics.on_ground;
|
||||
if physics.in_liquid.is_some() {
|
||||
internal_state.in_water = true;
|
||||
} else {
|
||||
internal_state.in_water = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +163,7 @@ impl MovementEventMapper {
|
||||
) -> bool {
|
||||
if let Some((event, item)) = sfx_trigger_item {
|
||||
if &previous_state.event == event {
|
||||
previous_state.time.elapsed().as_secs_f64() >= item.threshold
|
||||
previous_state.time.elapsed().as_secs_f32() >= item.threshold
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -153,9 +183,14 @@ impl MovementEventMapper {
|
||||
physics_state: &PhysicsState,
|
||||
previous_state: &PreviousEntityState,
|
||||
vel: Vec3<f32>,
|
||||
underfoot_block_kind: BlockKind,
|
||||
) -> SfxEvent {
|
||||
// Match run / roll state
|
||||
if physics_state.on_ground && vel.magnitude() > 0.1
|
||||
// Match run / roll / swim state
|
||||
if physics_state.in_liquid.is_some() && vel.magnitude() > 0.1
|
||||
|| !previous_state.in_water && physics_state.in_liquid.is_some()
|
||||
{
|
||||
return SfxEvent::Swim;
|
||||
} else if physics_state.on_ground && vel.magnitude() > 0.1
|
||||
|| !previous_state.on_ground && physics_state.on_ground
|
||||
{
|
||||
return if matches!(character_state, CharacterState::Roll(_)) {
|
||||
@ -163,7 +198,10 @@ impl MovementEventMapper {
|
||||
} else if matches!(character_state, CharacterState::Sneak) {
|
||||
SfxEvent::Sneak
|
||||
} else {
|
||||
SfxEvent::Run
|
||||
match underfoot_block_kind {
|
||||
BlockKind::Snow => SfxEvent::SnowRun,
|
||||
_ => SfxEvent::Run,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -183,9 +221,36 @@ impl MovementEventMapper {
|
||||
}
|
||||
|
||||
/// Maps a limited set of movements for other non-humanoid entities
|
||||
fn map_non_humanoid_movement_event(physics_state: &PhysicsState, vel: Vec3<f32>) -> SfxEvent {
|
||||
if physics_state.on_ground && vel.magnitude() > 0.1 {
|
||||
SfxEvent::Run
|
||||
fn map_non_humanoid_movement_event(
|
||||
physics_state: &PhysicsState,
|
||||
vel: Vec3<f32>,
|
||||
underfoot_block_kind: BlockKind,
|
||||
) -> SfxEvent {
|
||||
if physics_state.in_liquid.is_some() && vel.magnitude() > 0.1 {
|
||||
SfxEvent::Swim
|
||||
} else if physics_state.on_ground && vel.magnitude() > 0.1 {
|
||||
match underfoot_block_kind {
|
||||
BlockKind::Snow => SfxEvent::SnowRun,
|
||||
_ => SfxEvent::Run,
|
||||
}
|
||||
} else {
|
||||
SfxEvent::Idle
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a limited set of movements for quadruped entities
|
||||
fn map_quadruped_movement_event(
|
||||
physics_state: &PhysicsState,
|
||||
vel: Vec3<f32>,
|
||||
underfoot_block_kind: BlockKind,
|
||||
) -> SfxEvent {
|
||||
if physics_state.in_liquid.is_some() && vel.magnitude() > 0.1 {
|
||||
SfxEvent::Swim
|
||||
} else if physics_state.on_ground && vel.magnitude() > 0.1 {
|
||||
match underfoot_block_kind {
|
||||
BlockKind::Snow => SfxEvent::QuadSnowRun,
|
||||
_ => SfxEvent::QuadRun,
|
||||
}
|
||||
} else {
|
||||
SfxEvent::Idle
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use common::{
|
||||
bird_small, humanoid, quadruped_medium, quadruped_small, Body, CharacterState, PhysicsState,
|
||||
},
|
||||
states,
|
||||
terrain::BlockKind,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@ -28,6 +29,7 @@ fn config_but_played_since_threshold_no_emit() {
|
||||
event: SfxEvent::Run,
|
||||
time: Instant::now(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
};
|
||||
|
||||
let result =
|
||||
@ -47,6 +49,7 @@ fn config_and_not_played_since_threshold_emits() {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
};
|
||||
|
||||
let result =
|
||||
@ -68,6 +71,7 @@ fn same_previous_event_elapsed_emits() {
|
||||
.checked_sub(Duration::from_millis(500))
|
||||
.unwrap(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
};
|
||||
|
||||
let result =
|
||||
@ -88,8 +92,10 @@ fn maps_idle() {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::zero(),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Idle);
|
||||
@ -107,8 +113,10 @@ fn maps_run_with_sufficient_velocity() {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::new(0.5, 0.8, 0.0),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Run);
|
||||
@ -126,8 +134,10 @@ fn does_not_map_run_with_insufficient_velocity() {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::new(0.02, 0.0001, 0.0),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Idle);
|
||||
@ -142,8 +152,10 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
on_ground: false,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::new(0.5, 0.8, 0.0),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Idle);
|
||||
@ -173,8 +185,10 @@ fn maps_roll() {
|
||||
event: SfxEvent::Run,
|
||||
time: Instant::now(),
|
||||
on_ground: true,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::new(0.5, 0.5, 0.0),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Roll);
|
||||
@ -192,8 +206,10 @@ fn maps_land_on_ground_to_run() {
|
||||
event: SfxEvent::Idle,
|
||||
time: Instant::now(),
|
||||
on_ground: false,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::zero(),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Run);
|
||||
@ -208,8 +224,10 @@ fn maps_glider_open() {
|
||||
event: SfxEvent::Jump,
|
||||
time: Instant::now(),
|
||||
on_ground: false,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::zero(),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::GliderOpen);
|
||||
@ -224,8 +242,10 @@ fn maps_glide() {
|
||||
event: SfxEvent::Glide,
|
||||
time: Instant::now(),
|
||||
on_ground: false,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::zero(),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Glide);
|
||||
@ -240,8 +260,10 @@ fn maps_glider_close_when_closing_mid_flight() {
|
||||
event: SfxEvent::Glide,
|
||||
time: Instant::now(),
|
||||
on_ground: false,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::zero(),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::GliderClose);
|
||||
@ -260,8 +282,10 @@ fn maps_glider_close_when_landing() {
|
||||
event: SfxEvent::Glide,
|
||||
time: Instant::now(),
|
||||
on_ground: false,
|
||||
in_water: false,
|
||||
},
|
||||
Vec3::zero(),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::GliderClose);
|
||||
@ -275,6 +299,7 @@ fn maps_quadrupeds_running() {
|
||||
..Default::default()
|
||||
},
|
||||
Vec3::new(0.5, 0.8, 0.0),
|
||||
BlockKind::Grass,
|
||||
);
|
||||
|
||||
assert_eq!(result, SfxEvent::Run);
|
||||
|
@ -1,82 +0,0 @@
|
||||
/// EventMapper::Progress watches the player entity's stats
|
||||
/// and triggers sfx for gaining experience and levelling up
|
||||
use super::EventMapper;
|
||||
|
||||
use crate::{
|
||||
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggers},
|
||||
scene::Camera,
|
||||
};
|
||||
|
||||
use common::{comp::Stats, event::EventBus, state::State};
|
||||
use specs::WorldExt;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct ProgressionState {
|
||||
level: u32,
|
||||
exp: u32,
|
||||
}
|
||||
|
||||
impl Default for ProgressionState {
|
||||
fn default() -> Self { Self { level: 1, exp: 0 } }
|
||||
}
|
||||
|
||||
pub struct ProgressionEventMapper {
|
||||
state: ProgressionState,
|
||||
}
|
||||
|
||||
impl EventMapper for ProgressionEventMapper {
|
||||
#[allow(clippy::op_ref)] // TODO: Pending review in #587
|
||||
fn maintain(
|
||||
&mut self,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
_camera: &Camera,
|
||||
triggers: &SfxTriggers,
|
||||
) {
|
||||
let ecs = state.ecs();
|
||||
|
||||
let next_state = ecs.read_storage::<Stats>().get(player_entity).map_or(
|
||||
ProgressionState::default(),
|
||||
|stats| ProgressionState {
|
||||
level: stats.level.level(),
|
||||
exp: stats.exp.current(),
|
||||
},
|
||||
);
|
||||
|
||||
if &self.state != &next_state {
|
||||
if let Some(mapped_event) = self.map_event(&next_state) {
|
||||
let sfx_trigger_item = triggers.get_trigger(&mapped_event);
|
||||
|
||||
if sfx_trigger_item.is_some() {
|
||||
ecs.read_resource::<EventBus<SfxEventItem>>()
|
||||
.emit_now(SfxEventItem::at_player_position(mapped_event));
|
||||
}
|
||||
}
|
||||
|
||||
self.state = next_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProgressionEventMapper {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: ProgressionState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
||||
fn map_event(&mut self, next_state: &ProgressionState) -> Option<SfxEvent> {
|
||||
let sfx_event = if next_state.level > self.state.level {
|
||||
Some(SfxEvent::LevelUp)
|
||||
} else if next_state.exp > self.state.exp {
|
||||
Some(SfxEvent::ExperienceGained)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
sfx_event
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)] mod tests;
|
@ -1,43 +0,0 @@
|
||||
use super::*;
|
||||
use crate::audio::sfx::SfxEvent;
|
||||
|
||||
#[test]
|
||||
fn no_change_returns_none() {
|
||||
let mut mapper = ProgressionEventMapper::new();
|
||||
let next_client_state = ProgressionState::default();
|
||||
|
||||
assert_eq!(mapper.map_event(&next_client_state), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_level_returns_levelup() {
|
||||
let mut mapper = ProgressionEventMapper::new();
|
||||
let next_client_state = ProgressionState { level: 2, exp: 0 };
|
||||
|
||||
assert_eq!(
|
||||
mapper.map_event(&next_client_state),
|
||||
Some(SfxEvent::LevelUp)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_exp_returns_expup() {
|
||||
let mut mapper = ProgressionEventMapper::new();
|
||||
let next_client_state = ProgressionState { level: 1, exp: 100 };
|
||||
|
||||
assert_eq!(
|
||||
mapper.map_event(&next_client_state),
|
||||
Some(SfxEvent::ExperienceGained)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn level_up_and_gained_exp_prioritises_levelup() {
|
||||
let mut mapper = ProgressionEventMapper::new();
|
||||
let next_client_state = ProgressionState { level: 2, exp: 100 };
|
||||
|
||||
assert_eq!(
|
||||
mapper.map_event(&next_client_state),
|
||||
Some(SfxEvent::LevelUp)
|
||||
);
|
||||
}
|
@ -82,24 +82,27 @@
|
||||
|
||||
mod event_mapper;
|
||||
|
||||
use crate::{audio::AudioFrontend, scene::Camera};
|
||||
use crate::{
|
||||
audio::AudioFrontend,
|
||||
scene::{Camera, Terrain},
|
||||
};
|
||||
|
||||
use client::Client;
|
||||
use common::{
|
||||
assets,
|
||||
comp::{
|
||||
item::{ItemKind, ToolKind},
|
||||
object, Body, CharacterAbilityType, InventoryUpdateEvent,
|
||||
},
|
||||
event::EventBus,
|
||||
outcome::Outcome,
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
};
|
||||
use event_mapper::SfxEventMapper;
|
||||
use hashbrown::HashMap;
|
||||
use rand::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use specs::WorldExt;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::warn;
|
||||
use vek::*;
|
||||
|
||||
/// We watch the states of nearby entities in order to emit SFX at their
|
||||
@ -110,8 +113,11 @@ use vek::*;
|
||||
const SFX_DIST_LIMIT_SQR: f32 = 20000.0;
|
||||
|
||||
pub struct SfxEventItem {
|
||||
/// The SFX event that triggers this sound
|
||||
pub sfx: SfxEvent,
|
||||
/// The position at which the sound should play
|
||||
pub pos: Option<Vec3<f32>>,
|
||||
/// The volume to play the sound at
|
||||
pub vol: Option<f32>,
|
||||
}
|
||||
|
||||
@ -131,8 +137,20 @@ impl SfxEventItem {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
||||
pub enum SfxEvent {
|
||||
Campfire,
|
||||
Embers,
|
||||
Birdcall,
|
||||
Owl,
|
||||
Cricket,
|
||||
Frog,
|
||||
Bees,
|
||||
RunningWater,
|
||||
Idle,
|
||||
Swim,
|
||||
Run,
|
||||
QuadRun,
|
||||
SnowRun,
|
||||
QuadSnowRun,
|
||||
Roll,
|
||||
Sneak,
|
||||
Climb,
|
||||
@ -155,6 +173,7 @@ pub enum SfxEvent {
|
||||
pub enum SfxInventoryEvent {
|
||||
Collected,
|
||||
CollectedTool(ToolKind),
|
||||
CollectedItem(String),
|
||||
CollectFailed,
|
||||
Consumed(String),
|
||||
Debug,
|
||||
@ -163,16 +182,23 @@ pub enum SfxInventoryEvent {
|
||||
Swapped,
|
||||
}
|
||||
|
||||
// TODO Move to a separate event mapper?
|
||||
impl From<&InventoryUpdateEvent> for SfxEvent {
|
||||
fn from(value: &InventoryUpdateEvent) -> Self {
|
||||
match value {
|
||||
InventoryUpdateEvent::Collected(item) => {
|
||||
// Handle sound effects for types of collected items, falling back to the
|
||||
// default Collected event
|
||||
// Handle sound effects for types of collected items, falling
|
||||
// back to the default Collected event
|
||||
match &item.kind() {
|
||||
ItemKind::Tool(tool) => {
|
||||
SfxEvent::Inventory(SfxInventoryEvent::CollectedTool(tool.kind.clone()))
|
||||
},
|
||||
ItemKind::Ingredient { kind } => match &kind[..] {
|
||||
"ShinyGem" => {
|
||||
SfxEvent::Inventory(SfxInventoryEvent::CollectedItem(kind.clone()))
|
||||
},
|
||||
_ => SfxEvent::Inventory(SfxInventoryEvent::Collected),
|
||||
},
|
||||
_ => SfxEvent::Inventory(SfxInventoryEvent::Collected),
|
||||
}
|
||||
},
|
||||
@ -193,8 +219,10 @@ impl From<&InventoryUpdateEvent> for SfxEvent {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SfxTriggerItem {
|
||||
/// A list of SFX filepaths for this event
|
||||
pub files: Vec<String>,
|
||||
pub threshold: f64,
|
||||
/// The time to wait before repeating this SfxEvent
|
||||
pub threshold: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
@ -209,70 +237,51 @@ impl SfxTriggers {
|
||||
}
|
||||
|
||||
pub struct SfxMgr {
|
||||
triggers: SfxTriggers,
|
||||
pub triggers: SfxTriggers,
|
||||
event_mapper: SfxEventMapper,
|
||||
}
|
||||
|
||||
impl SfxMgr {
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self {
|
||||
impl Default for SfxMgr {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
triggers: Self::load_sfx_items(),
|
||||
event_mapper: SfxEventMapper::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SfxMgr {
|
||||
pub fn maintain(
|
||||
&mut self,
|
||||
audio: &mut AudioFrontend,
|
||||
state: &State,
|
||||
player_entity: specs::Entity,
|
||||
camera: &Camera,
|
||||
terrain: &Terrain<TerrainChunk>,
|
||||
client: &Client,
|
||||
) {
|
||||
// Checks if the SFX volume is set to zero or audio is disabled
|
||||
// This prevents us from running all the following code unnecessarily
|
||||
if !audio.sfx_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ecs = state.ecs();
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
|
||||
// Sets the listener position to the camera position facing the
|
||||
// same direction as the camera
|
||||
audio.set_listener_pos(cam_pos, camera.dependents().cam_dir);
|
||||
|
||||
// TODO: replace; deprecated in favor of outcomes
|
||||
self.event_mapper
|
||||
.maintain(state, player_entity, camera, &self.triggers);
|
||||
|
||||
// TODO: replace; deprecated in favor of outcomes
|
||||
let events = ecs.read_resource::<EventBus<SfxEventItem>>().recv_all();
|
||||
|
||||
for event in events {
|
||||
let position = match event.pos {
|
||||
Some(pos) => pos,
|
||||
_ => cam_pos,
|
||||
};
|
||||
|
||||
if let Some(item) = self.triggers.get_trigger(&event.sfx) {
|
||||
let sfx_file = match item.files.len() {
|
||||
0 => {
|
||||
debug!("Sfx event {:?} is missing audio file.", event.sfx);
|
||||
"voxygen.audio.sfx.placeholder"
|
||||
},
|
||||
1 => item
|
||||
.files
|
||||
.last()
|
||||
.expect("Failed to determine sound file for this trigger item."),
|
||||
_ => {
|
||||
let rand_step = rand::random::<usize>() % item.files.len();
|
||||
&item.files[rand_step]
|
||||
},
|
||||
};
|
||||
|
||||
audio.play_sfx(sfx_file, position, event.vol);
|
||||
} else {
|
||||
debug!("Missing sfx trigger config for sfx event. {:?}", event.sfx);
|
||||
}
|
||||
}
|
||||
self.event_mapper.maintain(
|
||||
audio,
|
||||
state,
|
||||
player_entity,
|
||||
camera,
|
||||
&self.triggers,
|
||||
terrain,
|
||||
client,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn handle_outcome(&mut self, outcome: &Outcome, audio: &mut AudioFrontend) {
|
||||
@ -304,7 +313,11 @@ impl SfxMgr {
|
||||
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
},
|
||||
Body::Object(object::Body::BoltFire | object::Body::BoltFireBig) => {
|
||||
Body::Object(
|
||||
object::Body::BoltFire
|
||||
| object::Body::BoltFireBig
|
||||
| object::Body::BoltNature,
|
||||
) => {
|
||||
let file_ref = vec![
|
||||
"voxygen.audio.sfx.abilities.fire_shot_1",
|
||||
"voxygen.audio.sfx.abilities.fire_shot_2",
|
||||
@ -317,6 +330,19 @@ impl SfxMgr {
|
||||
},
|
||||
}
|
||||
},
|
||||
Outcome::LevelUp { pos } => {
|
||||
let file_ref = "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
},
|
||||
Outcome::Beam { pos, heal } => {
|
||||
if *heal {
|
||||
let file_ref = "voxygen.audio.sfx.abilities.staff_channeling";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
} else {
|
||||
let file_ref = "voxygen.audio.sfx.abilities.flame_thrower";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ pub mod comp;
|
||||
pub mod sys;
|
||||
|
||||
use crate::audio::sfx::SfxEventItem;
|
||||
use common::event::EventBus;
|
||||
use common::{event::EventBus, outcome::Outcome};
|
||||
use specs::{Entity, World, WorldExt};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@ -27,6 +27,7 @@ pub fn init(world: &mut World) {
|
||||
world.register::<comp::HpFloaterList>();
|
||||
world.register::<comp::Interpolated>();
|
||||
world.insert(MyExpFloaterList::default());
|
||||
world.insert(Vec::<Outcome>::new());
|
||||
|
||||
// Voxygen event buses
|
||||
world.insert(EventBus::<SfxEventItem>::default());
|
||||
|
@ -319,7 +319,7 @@ pub enum Event {
|
||||
AdjustFigureLoDRenderDistance(u32),
|
||||
AdjustMusicVolume(f32),
|
||||
AdjustSfxVolume(f32),
|
||||
ChangeAudioDevice(String),
|
||||
//ChangeAudioDevice(String),
|
||||
ChangeMaxFPS(u32),
|
||||
ChangeFOV(u16),
|
||||
ChangeGamma(f32),
|
||||
@ -2131,9 +2131,9 @@ impl Hud {
|
||||
settings_window::Event::MaximumFPS(max_fps) => {
|
||||
events.push(Event::ChangeMaxFPS(max_fps));
|
||||
},
|
||||
settings_window::Event::ChangeAudioDevice(name) => {
|
||||
events.push(Event::ChangeAudioDevice(name));
|
||||
},
|
||||
//settings_window::Event::ChangeAudioDevice(name) => {
|
||||
// events.push(Event::ChangeAudioDevice(name));
|
||||
//},
|
||||
settings_window::Event::CrosshairType(crosshair_type) => {
|
||||
events.push(Event::CrosshairType(crosshair_type));
|
||||
},
|
||||
|
@ -291,7 +291,7 @@ pub enum Event {
|
||||
ChangeRenderMode(Box<RenderMode>),
|
||||
AdjustMusicVolume(f32),
|
||||
AdjustSfxVolume(f32),
|
||||
ChangeAudioDevice(String),
|
||||
//ChangeAudioDevice(String),
|
||||
MaximumFPS(u32),
|
||||
CrosshairTransp(f32),
|
||||
CrosshairType(CrosshairType),
|
||||
@ -2693,30 +2693,32 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
events.push(Event::AdjustSfxVolume(new_val));
|
||||
}
|
||||
|
||||
// Audio Device Selector --------------------------------------------
|
||||
let device = &self.global_state.audio.device;
|
||||
let device_list = &self.global_state.audio.device_list;
|
||||
Text::new(&self.localized_strings.get("hud.settings.audio_device"))
|
||||
.down_from(state.ids.sfx_volume_slider, 10.0)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.audio_device_text, ui);
|
||||
// Audio Device Selector
|
||||
// --------------------------------------------
|
||||
// let device = &self.global_state.audio.device;
|
||||
//let device_list = &self.global_state.audio.device_list;
|
||||
//Text::new(&self.localized_strings.get("hud.settings.audio_device"
|
||||
// )) .down_from(state.ids.sfx_volume_slider, 10.0)
|
||||
// .font_size(self.fonts.cyri.scale(14))
|
||||
// .font_id(self.fonts.cyri.conrod_id)
|
||||
// .color(TEXT_COLOR)
|
||||
// .set(state.ids.audio_device_text, ui);
|
||||
|
||||
// Get which device is currently selected
|
||||
let selected = device_list.iter().position(|x| x.contains(device));
|
||||
//// Get which device is currently selected
|
||||
//let selected = device_list.iter().position(|x|
|
||||
// x.contains(device));
|
||||
|
||||
if let Some(clicked) = DropDownList::new(&device_list, selected)
|
||||
.w_h(400.0, 22.0)
|
||||
.color(MENU_BG)
|
||||
.label_color(TEXT_COLOR)
|
||||
.label_font_id(self.fonts.opensans.conrod_id)
|
||||
.down_from(state.ids.audio_device_text, 10.0)
|
||||
.set(state.ids.audio_device_list, ui)
|
||||
{
|
||||
let new_val = device_list[clicked].clone();
|
||||
events.push(Event::ChangeAudioDevice(new_val));
|
||||
}
|
||||
//if let Some(clicked) = DropDownList::new(&device_list, selected)
|
||||
// .w_h(400.0, 22.0)
|
||||
// .color(MENU_BG)
|
||||
// .label_color(TEXT_COLOR)
|
||||
// .label_font_id(self.fonts.opensans.conrod_id)
|
||||
// .down_from(state.ids.audio_device_text, 10.0)
|
||||
// .set(state.ids.audio_device_list, ui)
|
||||
//{
|
||||
// let new_val = device_list[clicked].clone();
|
||||
// events.push(Event::ChangeAudioDevice(new_val));
|
||||
//}
|
||||
}
|
||||
|
||||
// 5) Languages Tab -----------------------------------
|
||||
|
@ -4,12 +4,12 @@
|
||||
#![recursion_limit = "2048"]
|
||||
|
||||
use veloren_voxygen::{
|
||||
audio::{self, AudioFrontend},
|
||||
audio::AudioFrontend,
|
||||
i18n::{self, i18n_asset_key, Localization},
|
||||
logging,
|
||||
profile::Profile,
|
||||
run,
|
||||
settings::{AudioOutput, Settings},
|
||||
settings::Settings,
|
||||
window::Window,
|
||||
GlobalState,
|
||||
};
|
||||
@ -142,13 +142,14 @@ fn main() {
|
||||
anim::init();
|
||||
|
||||
// Setup audio
|
||||
let mut audio = match settings.audio.output {
|
||||
AudioOutput::Off => None,
|
||||
AudioOutput::Automatic => audio::get_default_device(),
|
||||
AudioOutput::Device(ref dev) => Some(dev.clone()),
|
||||
}
|
||||
.map(|dev| AudioFrontend::new(dev, settings.audio.max_sfx_channels))
|
||||
.unwrap_or_else(AudioFrontend::no_audio);
|
||||
//let mut audio = match settings.audio.output {
|
||||
// AudioOutput::Off => None,
|
||||
// AudioOutput::Automatic => audio::get_default_device(),
|
||||
// AudioOutput::Device(ref dev) => Some(dev.clone()),
|
||||
//}
|
||||
//.map(|dev| AudioFrontend::new(dev, settings.audio.max_sfx_channels))
|
||||
//.unwrap_or_else(AudioFrontend::no_audio);
|
||||
let mut audio = AudioFrontend::new(settings.audio.max_sfx_channels);
|
||||
|
||||
audio.set_music_volume(settings.audio.music_volume);
|
||||
audio.set_sfx_volume(settings.audio.sfx_volume);
|
||||
|
@ -14,7 +14,7 @@ pub use self::{
|
||||
terrain::Terrain,
|
||||
};
|
||||
use crate::{
|
||||
audio::{music::MusicMgr, sfx::SfxMgr, AudioFrontend},
|
||||
audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
|
||||
render::{
|
||||
create_clouds_mesh, create_pp_mesh, create_skybox_mesh, CloudsLocals, CloudsPipeline,
|
||||
Consts, GlobalModel, Globals, Light, LodData, Model, PostProcessLocals,
|
||||
@ -101,8 +101,9 @@ pub struct Scene {
|
||||
|
||||
particle_mgr: ParticleMgr,
|
||||
figure_mgr: FigureMgr,
|
||||
sfx_mgr: SfxMgr,
|
||||
pub sfx_mgr: SfxMgr,
|
||||
music_mgr: MusicMgr,
|
||||
ambient_mgr: AmbientMgr,
|
||||
}
|
||||
|
||||
pub struct SceneData<'a> {
|
||||
@ -306,8 +307,9 @@ impl Scene {
|
||||
light_data: Vec::new(),
|
||||
particle_mgr: ParticleMgr::new(renderer),
|
||||
figure_mgr: FigureMgr::new(renderer),
|
||||
sfx_mgr: SfxMgr::new(),
|
||||
music_mgr: MusicMgr::new(),
|
||||
sfx_mgr: SfxMgr::default(),
|
||||
music_mgr: MusicMgr::default(),
|
||||
ambient_mgr: AmbientMgr::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,6 +433,7 @@ impl Scene {
|
||||
fadeout: |timeout| timeout * 2.0,
|
||||
}),
|
||||
Outcome::ProjectileShot { .. } => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,6 +444,7 @@ impl Scene {
|
||||
renderer: &mut Renderer,
|
||||
audio: &mut AudioFrontend,
|
||||
scene_data: &SceneData,
|
||||
client: &Client,
|
||||
) {
|
||||
span!(_guard, "maintain", "Scene::maintain");
|
||||
// Get player position.
|
||||
@ -993,8 +997,12 @@ impl Scene {
|
||||
scene_data.state,
|
||||
scene_data.player_entity,
|
||||
&self.camera,
|
||||
&self.terrain,
|
||||
client,
|
||||
);
|
||||
self.music_mgr.maintain(audio, scene_data.state);
|
||||
self.music_mgr.maintain(audio, scene_data.state, client);
|
||||
self.ambient_mgr
|
||||
.maintain(audio, scene_data.state, client, &self.camera);
|
||||
}
|
||||
|
||||
/// Render the scene using the provided `Renderer`.
|
||||
|
@ -124,6 +124,7 @@ impl ParticleMgr {
|
||||
}
|
||||
},
|
||||
Outcome::ProjectileShot { .. } => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ use vek::*;
|
||||
pub struct BlocksOfInterest {
|
||||
pub leaves: Vec<Vec3<i32>>,
|
||||
pub grass: Vec<Vec3<i32>>,
|
||||
pub river: Vec<Vec3<i32>>,
|
||||
pub embers: Vec<Vec3<i32>>,
|
||||
pub beehives: Vec<Vec3<i32>>,
|
||||
pub reeds: Vec<Vec3<i32>>,
|
||||
@ -23,6 +24,7 @@ impl BlocksOfInterest {
|
||||
span!(_guard, "from_chunk", "BlocksOfInterest::from_chunk");
|
||||
let mut leaves = Vec::new();
|
||||
let mut grass = Vec::new();
|
||||
let mut river = Vec::new();
|
||||
let mut embers = Vec::new();
|
||||
let mut beehives = Vec::new();
|
||||
let mut reeds = Vec::new();
|
||||
@ -50,6 +52,11 @@ impl BlocksOfInterest {
|
||||
grass.push(pos)
|
||||
}
|
||||
},
|
||||
BlockKind::Water => {
|
||||
if chunk.meta().contains_river() && thread_rng().gen_range(0, 16) == 0 {
|
||||
river.push(pos)
|
||||
}
|
||||
},
|
||||
_ => match block.get_sprite() {
|
||||
Some(SpriteKind::Ember) => embers.push(pos),
|
||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||
@ -71,6 +78,7 @@ impl BlocksOfInterest {
|
||||
Self {
|
||||
leaves,
|
||||
grass,
|
||||
river,
|
||||
embers,
|
||||
beehives,
|
||||
reeds,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
audio::sfx::{SfxEvent, SfxEventItem},
|
||||
audio::sfx::SfxEvent,
|
||||
ecs::MyEntity,
|
||||
hud::{DebugInfo, Event as HudEvent, Hud, HudInfo, PressBehavior},
|
||||
i18n::{i18n_asset_key, Localization},
|
||||
@ -7,7 +7,7 @@ use crate::{
|
||||
menu::char_selection::CharSelectionState,
|
||||
render::Renderer,
|
||||
scene::{camera, CameraMode, Scene, SceneData},
|
||||
settings::{AudioOutput, ControlSettings, Settings},
|
||||
settings::{ControlSettings, Settings},
|
||||
window::{AnalogGameInput, Event, GameInput},
|
||||
Direction, Error, GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
@ -17,7 +17,6 @@ use common::{
|
||||
comp,
|
||||
comp::{ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel},
|
||||
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||
event::EventBus,
|
||||
msg::PresenceKind,
|
||||
outcome::Outcome,
|
||||
span,
|
||||
@ -121,12 +120,12 @@ impl SessionState {
|
||||
self.hud.new_message(m);
|
||||
},
|
||||
client::Event::InventoryUpdated(inv_event) => {
|
||||
let sfx_event = SfxEvent::from(&inv_event);
|
||||
client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_resource::<EventBus<SfxEventItem>>()
|
||||
.emit_now(SfxEventItem::at_player_position(sfx_event));
|
||||
let sfx_trigger_item = self
|
||||
.scene
|
||||
.sfx_mgr
|
||||
.triggers
|
||||
.get_key_value(&SfxEvent::from(&inv_event));
|
||||
global_state.audio.emit_sfx_item(sfx_trigger_item);
|
||||
|
||||
match inv_event {
|
||||
InventoryUpdateEvent::CollectFailed => {
|
||||
@ -901,12 +900,12 @@ impl PlayState for SessionState {
|
||||
global_state.settings.audio.sfx_volume = sfx_volume;
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
HudEvent::ChangeAudioDevice(name) => {
|
||||
global_state.audio.set_device(name.clone());
|
||||
//HudEvent::ChangeAudioDevice(name) => {
|
||||
// global_state.audio.set_device(name.clone());
|
||||
|
||||
global_state.settings.audio.output = AudioOutput::Device(name);
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
// global_state.settings.audio.output = AudioOutput::Device(name);
|
||||
// global_state.settings.save_to_file_warn();
|
||||
//},
|
||||
HudEvent::ChangeMaxFPS(fps) => {
|
||||
global_state.settings.graphics.max_fps = fps;
|
||||
global_state.settings.save_to_file_warn();
|
||||
@ -1077,6 +1076,7 @@ impl PlayState for SessionState {
|
||||
global_state.window.renderer_mut(),
|
||||
&mut global_state.audio,
|
||||
&scene_data,
|
||||
&client,
|
||||
);
|
||||
|
||||
// Process outcomes from client
|
||||
|
@ -654,7 +654,6 @@ pub enum AudioOutput {
|
||||
// If this option is disabled, functions in the rodio
|
||||
// library MUST NOT be called.
|
||||
Off,
|
||||
Device(String),
|
||||
#[serde(other)]
|
||||
Automatic,
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ impl<'a> BlockGen<'a> {
|
||||
// temp,
|
||||
// humidity,
|
||||
stone_col,
|
||||
snow_cover,
|
||||
..
|
||||
} = sample;
|
||||
|
||||
@ -128,7 +129,10 @@ impl<'a> BlockGen<'a> {
|
||||
let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
|
||||
// Surface
|
||||
Some(Block::new(
|
||||
if grass_factor > 0.7 {
|
||||
if snow_cover {
|
||||
//if temp < CONFIG.snow_temp + 0.031 {
|
||||
BlockKind::Snow
|
||||
} else if grass_factor > 0.7 {
|
||||
BlockKind::Grass
|
||||
} else {
|
||||
BlockKind::Earth
|
||||
|
@ -155,7 +155,14 @@ impl World {
|
||||
},
|
||||
};
|
||||
|
||||
let meta = TerrainChunkMeta::new(sim_chunk.get_name(&self.sim), sim_chunk.get_biome());
|
||||
let meta = TerrainChunkMeta::new(
|
||||
sim_chunk.get_name(&self.sim),
|
||||
sim_chunk.get_biome(),
|
||||
sim_chunk.alt,
|
||||
sim_chunk.tree_density,
|
||||
sim_chunk.cave.1.alt != 0.0,
|
||||
sim_chunk.river.is_river(),
|
||||
);
|
||||
|
||||
let mut chunk = TerrainChunk::new(base_z, stone, air, meta);
|
||||
|
||||
|
@ -2303,14 +2303,20 @@ impl SimChunk {
|
||||
pub fn get_biome(&self) -> BiomeKind {
|
||||
if self.alt < CONFIG.sea_level {
|
||||
BiomeKind::Ocean
|
||||
} else if self.chaos > 0.6 {
|
||||
BiomeKind::Mountain
|
||||
} else if self.temp > CONFIG.desert_temp {
|
||||
BiomeKind::Desert
|
||||
} else if (self.temp - 0.5) < 0.005 && self.humidity < 0.1 {
|
||||
BiomeKind::Lake
|
||||
} else if self.temp < CONFIG.snow_temp {
|
||||
BiomeKind::Snowlands
|
||||
} else if self.tree_density > 0.65 {
|
||||
BiomeKind::Snowland
|
||||
} else if self.alt > 450.0 && self.chaos > 0.3 && self.tree_density < 0.6 {
|
||||
BiomeKind::Mountain
|
||||
} else if self.temp > CONFIG.desert_temp && self.humidity < 0.6 {
|
||||
BiomeKind::Desert
|
||||
//} else if self.tree_density > 0.65 && self.humidity > 0.7 && self.temp > 0.8 {
|
||||
// BiomeKind::Jungle
|
||||
} else if self.tree_density > 0.5 {
|
||||
BiomeKind::Forest
|
||||
//} else if self.humidity > 0.8 {
|
||||
// BiomeKind::Swamp
|
||||
} else {
|
||||
BiomeKind::Grassland
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user