diff --git a/.gitlab/CI/build.gitlab-ci.yml b/.gitlab/CI/build.gitlab-ci.yml index 8717d04f0e..1683d4c7b1 100644 --- a/.gitlab/CI/build.gitlab-ci.yml +++ b/.gitlab/CI/build.gitlab-ci.yml @@ -1,8 +1,11 @@ unittests: extends: .recompile-branch stage: build + variables: + GIT_DEPTH: 9999999999999 script: - ln -s /dockercache/cache-all target + - cargo test --package veloren-voxygen --lib test_all_localizations -- --nocapture --ignored - cargo test retry: max: 2 @@ -17,16 +20,6 @@ benchmarks: retry: max: 2 -localization-status: - extends: .recompile-branch - stage: build - variables: - GIT_DEPTH: 0 - allow_failure: true - script: - - ln -s /dockercache/cache-all target - - cargo test test_all_localizations -- --nocapture --ignored - # Coverage is needed on master for the README.md badge to work coverage: extends: .recompile @@ -110,4 +103,4 @@ opt-windows: opt-macos: extends: - .tmacos - - .optional-release \ No newline at end of file + - .optional-release diff --git a/.gitlab/CI/release.yml b/.gitlab/CI/release.yml index 5f0dfc86ea..7a59bd5b60 100644 --- a/.gitlab/CI/release.yml +++ b/.gitlab/CI/release.yml @@ -1,6 +1,6 @@ # allow_failure: true makes these pipelines manual and "non-blocking" which changed with except -> rule syntax .optional-release: - stage: build + stage: check tags: - veloren-docker rules: diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a842d4df..48b76f69e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Training dummy items - Added spin attack for axe - Creature specific stats +- Minimap compass +- Initial crafting system implementation +- Protection stat to armor that reduces incoming damage +- Loading-Screen tips ### Changed @@ -68,6 +72,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Animals are more effective in combat - Pathfinding is much smoother and pets are cleverer - Animals run/turn at different speeds +- Updated windowing library (winit 0.19 -> 0.22) +- Bow M2 is now a charged attack that scales the longer it's held ### Removed diff --git a/Cargo.lock b/Cargo.lock index 57a0977d4a..ec1bb05f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" +[[package]] +name = "android_log-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" + [[package]] name = "ansi_term" version = "0.11.0" @@ -108,6 +114,18 @@ dependencies = [ "syn 1.0.33", ] +[[package]] +name = "arraygen" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc755b23c19211c270ef000fa7ce871377825e6cc7d1bfd0311076f22c5e6ba1" +dependencies = [ + "proc-macro-error", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -416,6 +434,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "calloop" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160" +dependencies = [ + "mio", + "mio-extras", + "nix 0.14.1", +] + [[package]] name = "cast" version = "0.2.3" @@ -451,11 +480,10 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cgl" -version = "0.2.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" dependencies = [ - "gleam", "libc", ] @@ -518,14 +546,14 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.18.5" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" +checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" dependencies = [ "bitflags", "block", - "core-foundation", - "core-graphics", + "core-foundation 0.6.4", + "core-graphics 0.17.3", "foreign-types", "libc", "objc", @@ -533,14 +561,14 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.19.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" +checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" dependencies = [ "bitflags", "block", - "core-foundation", - "core-graphics", + "core-foundation 0.7.0", + "core-graphics 0.19.2", "foreign-types", "libc", "objc", @@ -549,7 +577,7 @@ dependencies = [ [[package]] name = "conrod_core" version = "0.63.0" -source = "git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927" +source = "git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30" dependencies = [ "conrod_derive", "copypasta", @@ -564,7 +592,7 @@ dependencies = [ [[package]] name = "conrod_derive" version = "0.63.0" -source = "git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927" +source = "git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", @@ -574,7 +602,7 @@ dependencies = [ [[package]] name = "conrod_winit" version = "0.63.0" -source = "git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927" +source = "git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30" [[package]] name = "const-random" @@ -651,7 +679,7 @@ dependencies = [ "objc-foundation", "objc_id", "smithay-clipboard", - "wayland-client 0.23.6", + "wayland-client", "x11-clipboard", ] @@ -661,7 +689,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.6.2", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", "libc", ] @@ -671,6 +709,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-graphics" version = "0.17.3" @@ -678,11 +722,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.6.4", "foreign-types", "libc", ] +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + [[package]] name = "coreaudio-rs" version = "0.9.1" @@ -709,7 +778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b55d55d69f403f62a95bd3c04b431e0aedf5120c70f15d07a8edd234443dd59" dependencies = [ "alsa-sys", - "core-foundation-sys", + "core-foundation-sys 0.6.2", "coreaudio-rs", "lazy_static", "libc", @@ -980,6 +1049,17 @@ dependencies = [ "byteorder 1.3.4", ] +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "deunicode" version = "1.1.1" @@ -1051,6 +1131,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04e93ca78226c51902d7aa8c12c988338aadd9e85ed9c6be8aaac39192ff3605" +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + [[package]] name = "dlib" version = "0.4.2" @@ -1494,17 +1580,6 @@ dependencies = [ "gl_generator 0.14.0", ] -[[package]] -name = "gfx_window_glutin" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310ff66f08b5a55854b18fea2f48bdbb75c94458207ba574c9723be78e97a646" -dependencies = [ - "gfx_core", - "gfx_device_gl", - "glutin", -] - [[package]] name = "gilrs" version = "0.7.4" @@ -1526,7 +1601,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c758daf46af26d6872fe55507e3b2339779a160a06ad7a9b2a082f221209cd" dependencies = [ - "core-foundation", + "core-foundation 0.6.4", "io-kit-sys", "libc", "libudev-sys", @@ -1610,15 +1685,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "gleam" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" -dependencies = [ - "gl_generator 0.13.1", -] - [[package]] name = "glib" version = "0.5.0" @@ -1661,15 +1727,15 @@ dependencies = [ [[package]] name = "glutin" -version = "0.21.2" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5371b35b309dace06be1b81b5f6adb1c9de578b7dbe1e74bf7e4ef762cf6febd" +checksum = "9a9666c8fd9afd008f6559e2468c35e11aad1d110d525bb3b354e4138ec0e20f" dependencies = [ "android_glue", "cgl", - "cocoa 0.18.5", - "core-foundation", - "core-graphics", + "cocoa 0.20.2", + "core-foundation 0.7.0", + "core-graphics 0.19.2", "glutin_egl_sys", "glutin_emscripten_sys", "glutin_gles2_sys", @@ -1677,10 +1743,11 @@ dependencies = [ "glutin_wgl_sys", "lazy_static", "libloading 0.5.2", + "log", "objc", "osmesa-sys", - "parking_lot 0.9.0", - "wayland-client 0.21.13", + "parking_lot 0.10.2", + "wayland-client", "winapi 0.3.8", "winit", ] @@ -2047,7 +2114,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f21dcc74995dd4cd090b147e79789f8d65959cbfb5f0b118002db869ea3bd0a0" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.6.2", "mach", ] @@ -2075,6 +2142,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.21" @@ -2447,6 +2520,37 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "ndk" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a356cafe20aee088789830bfea3a61336e84ded9e545e00d3869ce95dcb80c" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum", +] + +[[package]] +name = "ndk-glue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1730ee2e3de41c3321160a6da815f008c4006d71b095880ea50e17cf52332b8" +dependencies = [ + "android_log-sys", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2820aca934aba5ed91c79acc72b6a44048ceacc5d36c035ed4e051f12d887d" + [[package]] name = "net2" version = "0.2.34" @@ -2637,6 +2741,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "objc" version = "0.2.7" @@ -2681,6 +2807,17 @@ dependencies = [ "byteorder 1.3.4", ] +[[package]] +name = "old_school_gfx_glutin_ext" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0557cea37cc48d238c938ded2873a6cc772704ee1eb01e832b43c2dd99624bc" +dependencies = [ + "gfx_core", + "gfx_device_gl", + "glutin", +] + [[package]] name = "once_cell" version = "1.4.0" @@ -2967,6 +3104,41 @@ dependencies = [ "log", ] +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", + "version_check 0.9.2", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", + "syn-mid", + "version_check 0.9.2", +] + [[package]] name = "proc-macro-hack" version = "0.5.16" @@ -3655,23 +3827,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -[[package]] -name = "smithay-client-toolkit" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" -dependencies = [ - "andrew", - "bitflags", - "dlib", - "lazy_static", - "memmap", - "nix 0.14.1", - "wayland-client 0.21.13", - "wayland-commons 0.21.13", - "wayland-protocols 0.21.13", -] - [[package]] name = "smithay-client-toolkit" version = "0.6.6" @@ -3684,8 +3839,8 @@ dependencies = [ "lazy_static", "memmap", "nix 0.14.1", - "wayland-client 0.23.6", - "wayland-protocols 0.23.6", + "wayland-client", + "wayland-protocols", ] [[package]] @@ -3695,7 +3850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917e8ec7f535cd1a6cbf749c8866c24d67c548a80ac48c8e88a182eab5c07bd1" dependencies = [ "nix 0.14.1", - "smithay-client-toolkit 0.6.6", + "smithay-client-toolkit", ] [[package]] @@ -3849,6 +4004,17 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "synstructure" version = "0.12.4" @@ -4407,6 +4573,7 @@ dependencies = [ name = "veloren-common" version = "0.6.0" dependencies = [ + "arraygen", "authc", "criterion", "crossbeam", @@ -4490,14 +4657,13 @@ dependencies = [ "crossbeam", "deunicode", "directories-next", - "dispatch", + "dispatch 0.1.4", "dot_vox", "euc", "failure", "gfx", "gfx_device_gl", "gfx_gl", - "gfx_window_glutin", "gilrs", "git2", "glsl-include", @@ -4507,6 +4673,7 @@ dependencies = [ "image", "msgbox", "num 0.2.1", + "old_school_gfx_glutin_ext", "rand 0.7.3", "rodio", "ron", @@ -4693,21 +4860,6 @@ version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" -[[package]] -name = "wayland-client" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49963e5f9eeaf637bfcd1b9f0701c99fd5cd05225eb51035550d4272806f2713" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix 0.14.1", - "wayland-commons 0.21.13", - "wayland-scanner 0.21.13", - "wayland-sys 0.21.13", -] - [[package]] name = "wayland-client" version = "0.23.6" @@ -4715,22 +4867,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1080ebe0efabcf12aef2132152f616038f2d7dcbbccf7b2d8c5270fe14bcda" dependencies = [ "bitflags", + "calloop", "downcast-rs", "libc", + "mio", "nix 0.14.1", - "wayland-commons 0.23.6", - "wayland-scanner 0.23.6", - "wayland-sys 0.23.6", -] - -[[package]] -name = "wayland-commons" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec" -dependencies = [ - "nix 0.14.1", - "wayland-sys 0.21.13", + "wayland-commons", + "wayland-scanner", + "wayland-sys", ] [[package]] @@ -4740,20 +4884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" dependencies = [ "nix 0.14.1", - "wayland-sys 0.23.6", -] - -[[package]] -name = "wayland-protocols" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afde2ea2a428eee6d7d2c8584fdbe8b82eee8b6c353e129a434cd6e07f42145" -dependencies = [ - "bitflags", - "wayland-client 0.21.13", - "wayland-commons 0.21.13", - "wayland-scanner 0.21.13", - "wayland-sys 0.21.13", + "wayland-sys", ] [[package]] @@ -4763,20 +4894,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cc286643656742777d55dc8e70d144fa4699e426ca8e9d4ef454f4bf15ffcf9" dependencies = [ "bitflags", - "wayland-client 0.23.6", - "wayland-commons 0.23.6", - "wayland-scanner 0.23.6", -] - -[[package]] -name = "wayland-scanner" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3828c568714507315ee425a9529edc4a4aa9901409e373e9e0027e7622b79e" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "xml-rs", + "wayland-client", + "wayland-commons", + "wayland-scanner", ] [[package]] @@ -4790,16 +4910,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "wayland-sys" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520ab0fd578017a0ee2206623ba9ef4afe5e8f23ca7b42f6acfba2f4e66b1628" -dependencies = [ - "dlib", - "lazy_static", -] - [[package]] name = "wayland-sys" version = "0.23.6" @@ -4884,26 +4994,31 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winit" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e96eb4bb472fa43e718e8fa4aef82f86cd9deac9483a1e1529230babdb394a8" +version = "0.22.2" +source = "git+https://github.com/Imberflur/winit.git?branch=macos-test#e98133adf2abbfc4368f6c069d0beb2b8b688b42" dependencies = [ - "android_glue", - "backtrace", "bitflags", - "cocoa 0.18.5", - "core-foundation", - "core-graphics", + "cocoa 0.20.2", + "core-foundation 0.7.0", + "core-graphics 0.19.2", + "core-video-sys", + "dispatch 0.2.0", + "instant", "lazy_static", "libc", "log", + "mio", + "mio-extras", + "ndk", + "ndk-glue", + "ndk-sys", "objc", - "parking_lot 0.9.0", + "parking_lot 0.10.2", "percent-encoding 2.1.0", "raw-window-handle", "serde", - "smithay-client-toolkit 0.4.6", - "wayland-client 0.21.13", + "smithay-client-toolkit", + "wayland-client", "winapi 0.3.8", "x11-dl", ] diff --git a/Cargo.toml b/Cargo.toml index 277aa6d984..b87d9807fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,7 @@ debug = false [profile.releasedebuginfo] inherits = 'release' debug = 1 + +# cpal conflict fix isn't released yet +[patch.crates-io] +winit = { git = "https://github.com/Imberflur/winit.git", branch = "macos-test" } diff --git a/assets/common/items/armor/back/admin.ron b/assets/common/items/armor/back/admin.ron index f07273b7c6..1e7e0a313b 100644 --- a/assets/common/items/armor/back/admin.ron +++ b/assets/common/items/armor/back/admin.ron @@ -1,8 +1,12 @@ Item( name: "Admin's Cape", - description: "Back\n\nArmor: 0\n\nWith great power comes\ngreat responsibility.\n\n", + description: "With great power comes\ngreat responsibility.", kind: Armor( - kind: Back(Admin), - stats: (20), + ( + kind: Back(Admin), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/back/dungeon_purple-0.ron b/assets/common/items/armor/back/dungeon_purple-0.ron new file mode 100644 index 0000000000..8e513848db --- /dev/null +++ b/assets/common/items/armor/back/dungeon_purple-0.ron @@ -0,0 +1,12 @@ +Item( + name: "Purple Cultist Cape", + description: "Smells like dark magic and candles.", + kind: Armor( + ( + kind: Back(DungPurp0), + stats: ( + protection: Normal(3.0), + ), + ) + ), +) diff --git a/assets/common/items/armor/back/short_0.ron b/assets/common/items/armor/back/short_0.ron index f1759c553d..a1093e90b8 100644 --- a/assets/common/items/armor/back/short_0.ron +++ b/assets/common/items/armor/back/short_0.ron @@ -1,8 +1,12 @@ Item( name: "Short leather Cape", - description: "Back\n\nArmor: 0\n\n", + description: "Probably made of the finest leather.", kind: Armor( - kind: Back(Short0), - stats: (20), + ( + kind: Back(Short0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/assassin.ron b/assets/common/items/armor/belt/assassin.ron index dc086baa18..2c00d9066a 100644 --- a/assets/common/items/armor/belt/assassin.ron +++ b/assets/common/items/armor/belt/assassin.ron @@ -1,8 +1,12 @@ Item( name: "Assassin Belt", - description: "Belt\n\nArmor: 0\n\nOnly the best for a member of the creed.\n\n", + description: "Only the best for a member of the creed.", kind: Armor( - kind: Belt(Assassin), - stats: (20), + ( + kind: Belt(Assassin), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/cloth_blue_0.ron b/assets/common/items/armor/belt/cloth_blue_0.ron index 1faaafb7f3..ef9085cf85 100644 --- a/assets/common/items/armor/belt/cloth_blue_0.ron +++ b/assets/common/items/armor/belt/cloth_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Linen Belt", - description: "Belt\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm", kind: Armor( - kind: Belt(ClothBlue0), - stats: (20), + ( + kind: Belt(ClothBlue0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/cloth_green_0.ron b/assets/common/items/armor/belt/cloth_green_0.ron index 15be2149ea..b4cba9679d 100644 --- a/assets/common/items/armor/belt/cloth_green_0.ron +++ b/assets/common/items/armor/belt/cloth_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Linen Belt", - description: "Belt\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Belt(ClothGreen0), - stats: (20), + ( + kind: Belt(ClothGreen0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/cloth_purple_0.ron b/assets/common/items/armor/belt/cloth_purple_0.ron index 7060fd9c83..54cc8647dc 100644 --- a/assets/common/items/armor/belt/cloth_purple_0.ron +++ b/assets/common/items/armor/belt/cloth_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Linen Belt", - description: "Belt\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Belt(ClothPurple0), - stats: (20), + ( + kind: Belt(ClothPurple0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/cultist_belt.ron b/assets/common/items/armor/belt/cultist_belt.ron index 1b2765d2c8..439851225a 100644 --- a/assets/common/items/armor/belt/cultist_belt.ron +++ b/assets/common/items/armor/belt/cultist_belt.ron @@ -1,8 +1,12 @@ Item( name: "Cultist Belt", - description: "Belt\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Belt(Cultist), - stats: (20), + ( + kind: Belt(Cultist), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/druid.ron b/assets/common/items/armor/belt/druid.ron index 0a761f49c0..941ebbe6ca 100644 --- a/assets/common/items/armor/belt/druid.ron +++ b/assets/common/items/armor/belt/druid.ron @@ -1,8 +1,12 @@ Item( name: "Druid's Belt", - description: "Twisted vines to keep everything secure.\n\n", + description: "Twisted vines to keep everything secure.", kind: Armor( - kind: Belt(Druid), - stats: (20), + ( + kind: Belt(Druid), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/leather_0.ron b/assets/common/items/armor/belt/leather_0.ron index 6ca49af665..6c7b499bfb 100644 --- a/assets/common/items/armor/belt/leather_0.ron +++ b/assets/common/items/armor/belt/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Swift Belt", - description: "Belt\n\nArmor: 0\n\nSwift like the wind.\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Belt(Leather0), - stats: (20), + ( + kind: Belt(Leather0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/leather_2.ron b/assets/common/items/armor/belt/leather_2.ron index ae3fd773f4..fb2d1d05a4 100644 --- a/assets/common/items/armor/belt/leather_2.ron +++ b/assets/common/items/armor/belt/leather_2.ron @@ -2,7 +2,11 @@ Item( name: "Leather Belt", description: "A belt made from simple leather.", kind: Armor( - kind: Belt(Leather2), - stats: (20), + ( + kind: Belt(Leather2), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/plate_0.ron b/assets/common/items/armor/belt/plate_0.ron index f1269346f1..c48c0bdb0a 100644 --- a/assets/common/items/armor/belt/plate_0.ron +++ b/assets/common/items/armor/belt/plate_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron Belt", - description: "Belt\n\nArmor: 0\n\n", + description: "A belt with a buckle forged from iron.", kind: Armor( - kind: Belt(Plate0), - stats: (20), + ( + kind: Belt(Plate0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/steel_0.ron b/assets/common/items/armor/belt/steel_0.ron index 03086db2f9..0b9d7df412 100644 --- a/assets/common/items/armor/belt/steel_0.ron +++ b/assets/common/items/armor/belt/steel_0.ron @@ -1,8 +1,12 @@ Item( name: "Steel Belt", - description: "A belt made from Steel.", + description: "A belt forged from steel.", kind: Armor( - kind: Belt(Steel0), - stats: (20), + ( + kind: Belt(Steel0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/twig.ron b/assets/common/items/armor/belt/twig.ron index 42c53c5dbb..754206366b 100644 --- a/assets/common/items/armor/belt/twig.ron +++ b/assets/common/items/armor/belt/twig.ron @@ -1,8 +1,12 @@ Item( name: "Twig Belt", - description: "A belt made from woven from twigs.\n\n", + description: "A belt made from woven from twigs.", kind: Armor( - kind: Belt(Twig), - stats: (20), + ( + kind: Belt(Twig), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/twigsflowers.ron b/assets/common/items/armor/belt/twigsflowers.ron index f11985c104..0d503ef7d7 100644 --- a/assets/common/items/armor/belt/twigsflowers.ron +++ b/assets/common/items/armor/belt/twigsflowers.ron @@ -1,8 +1,12 @@ Item( name: "Flowery Belt", - description: "A belt woven from twigs and flowers.\n\n", + description: "A belt woven from twigs and flowers.", kind: Armor( - kind: Belt(Twigsflowers), - stats: (20), + ( + kind: Belt(Twigsflowers), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/belt/twigsleaves.ron b/assets/common/items/armor/belt/twigsleaves.ron index 5f00fce3e8..883f1ebe77 100644 --- a/assets/common/items/armor/belt/twigsleaves.ron +++ b/assets/common/items/armor/belt/twigsleaves.ron @@ -1,8 +1,12 @@ Item( name: "Leafy Belt", - description: "A belt woven from twigs and leaves.\n\n", + description: "A belt woven from twigs and leaves.", kind: Armor( - kind: Belt(Twigsleaves), - stats: (20), + ( + kind: Belt(Twigsleaves), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/assassin.ron b/assets/common/items/armor/chest/assassin.ron index ba969ddd30..cbe034b1f1 100644 --- a/assets/common/items/armor/chest/assassin.ron +++ b/assets/common/items/armor/chest/assassin.ron @@ -1,8 +1,12 @@ Item( name: "Assassin Chest", - description: "Chest\n\nArmor: 0\n\nOnly the best for a member of the creed.\n\n", + description: "Only the best for a member of the creed.", kind: Armor( - kind: Chest(Assassin), - stats: (20), + ( + kind: Chest(Assassin), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/cloth_blue_0.ron b/assets/common/items/armor/chest/cloth_blue_0.ron index a36d093faf..d2a69a427c 100644 --- a/assets/common/items/armor/chest/cloth_blue_0.ron +++ b/assets/common/items/armor/chest/cloth_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Linen Chest", - description: "Chest\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Chest(ClothBlue0), - stats: (20), + ( + kind: Chest(ClothBlue0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/cloth_green_0.ron b/assets/common/items/armor/chest/cloth_green_0.ron index 0098c1b869..840c3f1c37 100644 --- a/assets/common/items/armor/chest/cloth_green_0.ron +++ b/assets/common/items/armor/chest/cloth_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Linen Chest", - description: "Chest\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Chest(ClothGreen0), - stats: (20), + ( + kind: Chest(ClothGreen0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/cloth_purple_0.ron b/assets/common/items/armor/chest/cloth_purple_0.ron index fef6404226..b77ab71883 100644 --- a/assets/common/items/armor/chest/cloth_purple_0.ron +++ b/assets/common/items/armor/chest/cloth_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Linen Chest", - description: "Chest\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Chest(ClothPurple0), - stats: (20), + ( + kind: Chest(ClothPurple0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/cultist_chest_blue.ron b/assets/common/items/armor/chest/cultist_chest_blue.ron index 2039ea6d76..8b1a34da39 100644 --- a/assets/common/items/armor/chest/cultist_chest_blue.ron +++ b/assets/common/items/armor/chest/cultist_chest_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Chest", - description: "Chest\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Chest(CultistBlue), - stats: (20), + ( + kind: Chest(CultistBlue), + stats: ( + protection: Normal(30.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/cultist_chest_purple.ron b/assets/common/items/armor/chest/cultist_chest_purple.ron index a3bf40b1ff..e35a727d6b 100644 --- a/assets/common/items/armor/chest/cultist_chest_purple.ron +++ b/assets/common/items/armor/chest/cultist_chest_purple.ron @@ -1,8 +1,12 @@ Item( name: "Purple Cultist Chest", - description: "Chest\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Chest(CultistPurple), - stats: (20), + ( + kind: Chest(CultistPurple), + stats: ( + protection: Normal(30.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/druid.ron b/assets/common/items/armor/chest/druid.ron index 71ad9e1bef..96855c249a 100644 --- a/assets/common/items/armor/chest/druid.ron +++ b/assets/common/items/armor/chest/druid.ron @@ -1,8 +1,12 @@ Item( name: "Druid's Vest", - description: "Druidic chest wrappings.\n\n", + description: "Druidic chest wrappings.", kind: Armor( - kind: Chest(Druid), - stats: (20), + ( + kind: Chest(Druid), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/leather_0.ron b/assets/common/items/armor/chest/leather_0.ron index 1135163213..21767b4c06 100644 --- a/assets/common/items/armor/chest/leather_0.ron +++ b/assets/common/items/armor/chest/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Swift Chest", - description: "Chest\n\nArmor: 0\n\nSwift like the wind.\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Chest(Leather0), - stats: (20), + ( + kind: Chest(Leather0), + stats: ( + protection: Normal(10.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/leather_2.ron b/assets/common/items/armor/chest/leather_2.ron index 1a046a7942..75661e56a0 100644 --- a/assets/common/items/armor/chest/leather_2.ron +++ b/assets/common/items/armor/chest/leather_2.ron @@ -1,8 +1,12 @@ Item( name: "Leather Cuirass", - description: "A cuirass made of simple leather", + description: "A cuirass made of simple leather.", kind: Armor( - kind: Chest(Leather2), - stats: (20), + ( + kind: Chest(Leather2), + stats: ( + protection: Normal(10.0), + ), + ) ), ) \ No newline at end of file diff --git a/assets/common/items/armor/chest/plate_green_0.ron b/assets/common/items/armor/chest/plate_green_0.ron index aabc7ab943..dda388739b 100644 --- a/assets/common/items/armor/chest/plate_green_0.ron +++ b/assets/common/items/armor/chest/plate_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron Chestplate", - description: "Chest\n\nArmor: 0\n\n", + description: "A chestplate forged from iron.", kind: Armor( - kind: Chest(PlateGreen0), - stats: (20), + ( + kind: Chest(PlateGreen0), + stats: ( + protection: Normal(20.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/steel_0.ron b/assets/common/items/armor/chest/steel_0.ron index c97a49a7c4..314bf57e95 100644 --- a/assets/common/items/armor/chest/steel_0.ron +++ b/assets/common/items/armor/chest/steel_0.ron @@ -1,8 +1,12 @@ Item( name: "Steel Cuirass", - description: "A cuirass of steel plate", + description: "A cuirass of steel plate.", kind: Armor( - kind: Chest(Steel0), - stats: (20), + ( + kind: Chest(Steel0), + stats: ( + protection: Normal(25.0), + ), + ) ), ) \ No newline at end of file diff --git a/assets/common/items/armor/chest/twig.ron b/assets/common/items/armor/chest/twig.ron index 6dd728f2bf..56c3584569 100644 --- a/assets/common/items/armor/chest/twig.ron +++ b/assets/common/items/armor/chest/twig.ron @@ -1,8 +1,12 @@ Item( name: "Twig Shirt", - description: "A shirt woven from twigs.\n\n", + description: "A shirt woven from twigs.", kind: Armor( - kind: Chest(Twig), - stats: (20), + ( + kind: Chest(Twig), + stats: ( + protection: Normal(15.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/twigsflowers.ron b/assets/common/items/armor/chest/twigsflowers.ron index 698ae8dc64..8b364b55e9 100644 --- a/assets/common/items/armor/chest/twigsflowers.ron +++ b/assets/common/items/armor/chest/twigsflowers.ron @@ -1,8 +1,12 @@ Item( name: "Flowery Shirt", - description: "A shirt woven from twigs and flowers.\n\n", + description: "A shirt woven from twigs and flowers.", kind: Armor( - kind: Chest(Twigsflowers), - stats: (20), + ( + kind: Chest(Twigsflowers), + stats: ( + protection: Normal(15.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/twigsleaves.ron b/assets/common/items/armor/chest/twigsleaves.ron index e1cc509b9e..336182d654 100644 --- a/assets/common/items/armor/chest/twigsleaves.ron +++ b/assets/common/items/armor/chest/twigsleaves.ron @@ -1,8 +1,12 @@ Item( name: "Leafy Shirt", - description: "A shirt woven from twigs and leaves.\n\n", + description: "A shirt woven from twigs and leaves.", kind: Armor( - kind: Chest(Twigsleaves), - stats: (20), + ( + kind: Chest(Twigsleaves), + stats: ( + protection: Normal(15.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_green_0.ron b/assets/common/items/armor/chest/worker_green_0.ron index d474d10ddf..90f884c50b 100644 --- a/assets/common/items/armor/chest/worker_green_0.ron +++ b/assets/common/items/armor/chest/worker_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerGreen0), - stats: (20), + ( + kind: Chest(WorkerGreen0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_green_1.ron b/assets/common/items/armor/chest/worker_green_1.ron index bae09a6fe9..5a52626017 100644 --- a/assets/common/items/armor/chest/worker_green_1.ron +++ b/assets/common/items/armor/chest/worker_green_1.ron @@ -1,8 +1,12 @@ Item( name: "Green Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerGreen1), - stats: (20), + ( + kind: Chest(WorkerGreen1), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_orange_0.ron b/assets/common/items/armor/chest/worker_orange_0.ron index 6de8ffca79..a65db86c4e 100644 --- a/assets/common/items/armor/chest/worker_orange_0.ron +++ b/assets/common/items/armor/chest/worker_orange_0.ron @@ -1,8 +1,12 @@ Item( name: "Orange Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerOrange0), - stats: (20), + ( + kind: Chest(WorkerOrange0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_orange_1.ron b/assets/common/items/armor/chest/worker_orange_1.ron index 02e1f57b77..b0e41761c1 100644 --- a/assets/common/items/armor/chest/worker_orange_1.ron +++ b/assets/common/items/armor/chest/worker_orange_1.ron @@ -1,8 +1,12 @@ Item( name: "Orange Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerOrange1), - stats: (20), + ( + kind: Chest(WorkerOrange1), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_purple_0.ron b/assets/common/items/armor/chest/worker_purple_0.ron index f9fc9b26a5..b8dd21d140 100644 --- a/assets/common/items/armor/chest/worker_purple_0.ron +++ b/assets/common/items/armor/chest/worker_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerPurple0), - stats: (20), + ( + kind: Chest(WorkerPurple0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_purple_1.ron b/assets/common/items/armor/chest/worker_purple_1.ron index b183b6607b..f6782a1632 100644 --- a/assets/common/items/armor/chest/worker_purple_1.ron +++ b/assets/common/items/armor/chest/worker_purple_1.ron @@ -1,8 +1,12 @@ Item( name: "Purple Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerPurple1), - stats: (20), + ( + kind: Chest(WorkerPurple1), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_red_0.ron b/assets/common/items/armor/chest/worker_red_0.ron index 0cbdffed32..62f3159ace 100644 --- a/assets/common/items/armor/chest/worker_red_0.ron +++ b/assets/common/items/armor/chest/worker_red_0.ron @@ -1,8 +1,12 @@ Item( name: "Red Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerRed0), - stats: (20), + ( + kind: Chest(WorkerRed0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_red_1.ron b/assets/common/items/armor/chest/worker_red_1.ron index 5edb6ef876..a1a3b5eda7 100644 --- a/assets/common/items/armor/chest/worker_red_1.ron +++ b/assets/common/items/armor/chest/worker_red_1.ron @@ -1,8 +1,12 @@ Item( name: "Red Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerRed1), - stats: (20), + ( + kind: Chest(WorkerRed1), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_yellow_0.ron b/assets/common/items/armor/chest/worker_yellow_0.ron index c79a242550..0d625111f8 100644 --- a/assets/common/items/armor/chest/worker_yellow_0.ron +++ b/assets/common/items/armor/chest/worker_yellow_0.ron @@ -1,8 +1,12 @@ Item( name: "Yellow Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerYellow0), - stats: (20), + ( + kind: Chest(WorkerYellow0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/chest/worker_yellow_1.ron b/assets/common/items/armor/chest/worker_yellow_1.ron index 2200e50e91..85d69c6a98 100644 --- a/assets/common/items/armor/chest/worker_yellow_1.ron +++ b/assets/common/items/armor/chest/worker_yellow_1.ron @@ -1,8 +1,12 @@ Item( name: "Yellow Worker Shirt", - description: "Chest\n\nArmor: 0\n\n", + description: "Was used by a farmer, until recently.", kind: Armor( - kind: Chest(WorkerYellow1), - stats: (20), + ( + kind: Chest(WorkerYellow1), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/assassin.ron b/assets/common/items/armor/foot/assassin.ron index 6fd150c332..2b3c47390c 100644 --- a/assets/common/items/armor/foot/assassin.ron +++ b/assets/common/items/armor/foot/assassin.ron @@ -1,8 +1,12 @@ Item( name: "Assassin Boots", - description: "Feet\n\nArmor: 0\n\nOnly the best for a member of the creed.\n\n", + description: "Only the best for a member of the creed.", kind: Armor( - kind: Foot(Assassin), - stats: (20), + ( + kind: Foot(Assassin), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/cloth_blue_0.ron b/assets/common/items/armor/foot/cloth_blue_0.ron index 77c9b299db..239b32ca05 100644 --- a/assets/common/items/armor/foot/cloth_blue_0.ron +++ b/assets/common/items/armor/foot/cloth_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Linen Boots", - description: "Feet\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Foot(ClothBlue0), - stats: (20), + ( + kind: Foot(ClothBlue0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/cloth_green_0.ron b/assets/common/items/armor/foot/cloth_green_0.ron index b2eccab1f2..66586701a0 100644 --- a/assets/common/items/armor/foot/cloth_green_0.ron +++ b/assets/common/items/armor/foot/cloth_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Linen Boots", - description: "Feet\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Foot(ClothGreen0), - stats: (20), + ( + kind: Foot(ClothGreen0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/cloth_purple_0.ron b/assets/common/items/armor/foot/cloth_purple_0.ron index 77db769d55..fcc1e9b15d 100644 --- a/assets/common/items/armor/foot/cloth_purple_0.ron +++ b/assets/common/items/armor/foot/cloth_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Linen Boots", - description: "Feet\n\nArmor: 0\n\nSoft and warm\n\n", + description: "Soft and warm.", kind: Armor( - kind: Foot(ClothPurple0), - stats: (20), + ( + kind: Foot(ClothPurple0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/cultist_boots.ron b/assets/common/items/armor/foot/cultist_boots.ron index 70d134e5fe..2f9453db96 100644 --- a/assets/common/items/armor/foot/cultist_boots.ron +++ b/assets/common/items/armor/foot/cultist_boots.ron @@ -1,8 +1,12 @@ Item( name: "Cultist Boots", - description: "Feet\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Foot(Cultist), - stats: (20), + ( + kind: Foot(Cultist), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/druid.ron b/assets/common/items/armor/foot/druid.ron index ceb8f5979f..51cd2072d4 100644 --- a/assets/common/items/armor/foot/druid.ron +++ b/assets/common/items/armor/foot/druid.ron @@ -1,8 +1,12 @@ Item( name: "Druid's Slippers", - description: "For treading softly through the woods.\n\n", + description: "For treading softly through the woods.", kind: Armor( - kind: Foot(Druid), - stats: (20), + ( + kind: Foot(Druid), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/jackalope_slippers.ron b/assets/common/items/armor/foot/jackalope_slippers.ron index bd0804c5d3..751dce12a3 100644 --- a/assets/common/items/armor/foot/jackalope_slippers.ron +++ b/assets/common/items/armor/foot/jackalope_slippers.ron @@ -1,8 +1,12 @@ Item( name: "Fluffy Jackalope Slippers", - description: "So warm and cozy!\n\n", + description: "So warm and cozy!", kind: Armor( - kind: Foot(JackalopeSlips), - stats: (20), + ( + kind: Foot(JackalopeSlips), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/leather_0.ron b/assets/common/items/armor/foot/leather_0.ron index 7e811efc3a..bd508944de 100644 --- a/assets/common/items/armor/foot/leather_0.ron +++ b/assets/common/items/armor/foot/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Swift Boots", - description: "Feet\n\nArmor: 0\n\nSwift like the wind.\nWon't make you run faster.\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Foot(Leather0), - stats: (20), + ( + kind: Foot(Leather0), + stats: ( + protection: Normal(2.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/leather_2.ron b/assets/common/items/armor/foot/leather_2.ron index ad91562bb3..6fe2ce0c6f 100644 --- a/assets/common/items/armor/foot/leather_2.ron +++ b/assets/common/items/armor/foot/leather_2.ron @@ -1,8 +1,12 @@ Item( name: "Leather Boots", - description: "Boots made of simple leather", + description: "Boots made of simple leather.", kind: Armor( - kind: Foot(Leather2), - stats: (20), + ( + kind: Foot(Leather2), + stats: ( + protection: Normal(2.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/plate_0.ron b/assets/common/items/armor/foot/plate_0.ron index 21dc0fe27c..fd9c41bb15 100644 --- a/assets/common/items/armor/foot/plate_0.ron +++ b/assets/common/items/armor/foot/plate_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron Feet", - description: "Feet\n\nArmor: 0\n\n", + description: "Boots forged from iron.", kind: Armor( - kind: Foot(Plate0), - stats: (20), + ( + kind: Foot(Plate0), + stats: ( + protection: Normal(4.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/steel_0.ron b/assets/common/items/armor/foot/steel_0.ron index ccd47f75fb..5b39db22e4 100644 --- a/assets/common/items/armor/foot/steel_0.ron +++ b/assets/common/items/armor/foot/steel_0.ron @@ -1,8 +1,12 @@ Item( name: "Steel Boots", - description: "Boots plated in steel", + description: "Boots forged from steel.", kind: Armor( - kind: Foot(Steel0), - stats: (20), + ( + kind: Foot(Steel0), + stats: ( + protection: Normal(5.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/twig.ron b/assets/common/items/armor/foot/twig.ron index e367a857dd..742c7b1cc9 100644 --- a/assets/common/items/armor/foot/twig.ron +++ b/assets/common/items/armor/foot/twig.ron @@ -1,8 +1,12 @@ Item( name: "Twig Boots", - description: "Boots woven from twigs.\n\n", + description: "Boots woven from twigs.", kind: Armor( - kind: Foot(Twig), - stats: (20), + ( + kind: Foot(Twig), + stats: ( + protection: Normal(3.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/twigsflowers.ron b/assets/common/items/armor/foot/twigsflowers.ron index fda0a894fc..ec1918a11b 100644 --- a/assets/common/items/armor/foot/twigsflowers.ron +++ b/assets/common/items/armor/foot/twigsflowers.ron @@ -1,8 +1,12 @@ Item( name: "Flowery Boots", - description: "Boots woven from twigs and flowers.\n\n", + description: "Boots woven from twigs and flowers.", kind: Armor( - kind: Foot(Twigsflowers), - stats: (20), + ( + kind: Foot(Twigsflowers), + stats: ( + protection: Normal(3.0), + ), + ) ), ) diff --git a/assets/common/items/armor/foot/twigsleaves.ron b/assets/common/items/armor/foot/twigsleaves.ron index 7aed59cedd..7dfa97ac34 100644 --- a/assets/common/items/armor/foot/twigsleaves.ron +++ b/assets/common/items/armor/foot/twigsleaves.ron @@ -1,8 +1,12 @@ Item( name: "Leafy Boots", - description: "Boots woven from twigs and leaves.\n\n", + description: "Boots woven from twigs and leaves.", kind: Armor( - kind: Foot(Twigsleaves), - stats: (20), + ( + kind: Foot(Twigsleaves), + stats: ( + protection: Normal(3.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/assassin.ron b/assets/common/items/armor/hand/assassin.ron index 8e923a13d4..9b635f7a0d 100644 --- a/assets/common/items/armor/hand/assassin.ron +++ b/assets/common/items/armor/hand/assassin.ron @@ -1,8 +1,12 @@ Item( name: "Assassin Gloves", - description: "Hands\n\nArmor: 0\n\nOnly the best for a member of the creed.\n\n", + description: "Only the best for a member of the creed.", kind: Armor( - kind: Hand(Assassin), - stats: (20), + ( + kind: Hand(Assassin), + stats: ( + protection: Normal(2.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/cloth_blue_0.ron b/assets/common/items/armor/hand/cloth_blue_0.ron index f692f8123f..9fcc19d577 100644 --- a/assets/common/items/armor/hand/cloth_blue_0.ron +++ b/assets/common/items/armor/hand/cloth_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Linen Wrists", - description: "Hands\n\nArmor: 0\n\n", + description: "A strip of cloth.", kind: Armor( - kind: Hand(ClothBlue0), - stats: (20), + ( + kind: Hand(ClothBlue0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/cloth_green_0.ron b/assets/common/items/armor/hand/cloth_green_0.ron index 5a20af9f12..face79a1d4 100644 --- a/assets/common/items/armor/hand/cloth_green_0.ron +++ b/assets/common/items/armor/hand/cloth_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Linen Wrists", - description: "Hands\n\nArmor: 0\n\n", + description: "A strip of cloth.", kind: Armor( - kind: Hand(ClothGreen0), - stats: (20), + ( + kind: Hand(ClothGreen0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/cloth_purple_0.ron b/assets/common/items/armor/hand/cloth_purple_0.ron index f4c99713f6..5946dddd2f 100644 --- a/assets/common/items/armor/hand/cloth_purple_0.ron +++ b/assets/common/items/armor/hand/cloth_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Silk Wrists", - description: "Hands\n\nArmor: 0\n\n", + description: "A strip of cloth.", kind: Armor( - kind: Hand(ClothPurple0), - stats: (20), + ( + kind: Hand(ClothPurple0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/cultist_hands_blue.ron b/assets/common/items/armor/hand/cultist_hands_blue.ron index c6d3076b58..c4c88cd131 100644 --- a/assets/common/items/armor/hand/cultist_hands_blue.ron +++ b/assets/common/items/armor/hand/cultist_hands_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Gloves", - description: "Hands\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Hand(CultistBlue), - stats: (20), + ( + kind: Hand(CultistBlue), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/cultist_hands_purple.ron b/assets/common/items/armor/hand/cultist_hands_purple.ron index fb89f73edd..46e1f672d7 100644 --- a/assets/common/items/armor/hand/cultist_hands_purple.ron +++ b/assets/common/items/armor/hand/cultist_hands_purple.ron @@ -1,8 +1,12 @@ Item( name: "Purple Cultist Gloves", - description: "Hands\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Hand(CultistPurple), - stats: (20), + ( + kind: Hand(CultistPurple), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/druid.ron b/assets/common/items/armor/hand/druid.ron index 63400b0f1f..ddb12753a7 100644 --- a/assets/common/items/armor/hand/druid.ron +++ b/assets/common/items/armor/hand/druid.ron @@ -1,8 +1,12 @@ Item( name: "Druid's Gloves", - description: "Soft, strong, and flexible.\n\n", + description: "Soft, strong, and flexible.", kind: Armor( - kind: Hand(Druid), - stats: (20), + ( + kind: Hand(Druid), + stats: ( + protection: Normal(2.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/leather_0.ron b/assets/common/items/armor/hand/leather_0.ron index d19024f3e7..050623f3c3 100644 --- a/assets/common/items/armor/hand/leather_0.ron +++ b/assets/common/items/armor/hand/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Swift Gloves", - description: "Hands\n\nArmor: 0\n\nSwift like the wind.\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Hand(Leather0), - stats: (20), + ( + kind: Hand(Leather0), + stats: ( + protection: Normal(4.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/leather_2.ron b/assets/common/items/armor/hand/leather_2.ron index 22b52d99a1..639b1ba37a 100644 --- a/assets/common/items/armor/hand/leather_2.ron +++ b/assets/common/items/armor/hand/leather_2.ron @@ -1,8 +1,12 @@ Item( name: "Leather Gloves", - description: "Gloves made of simple leather", + description: "Gloves made of simple leather.", kind: Armor( - kind: Hand(Leather2), - stats: (20), + ( + kind: Hand(Leather2), + stats: ( + protection: Normal(4.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/plate_0.ron b/assets/common/items/armor/hand/plate_0.ron index 516aab8625..20242b87ed 100644 --- a/assets/common/items/armor/hand/plate_0.ron +++ b/assets/common/items/armor/hand/plate_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron Handguards", - description: "Hands\n\nArmor: 0\n\n", + description: "Gauntlets forged from iron.", kind: Armor( - kind: Hand(Plate0), - stats: (20), + ( + kind: Hand(Plate0), + stats: ( + protection: Normal(8.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/steel_0.ron b/assets/common/items/armor/hand/steel_0.ron index ad7e237adc..6978576802 100644 --- a/assets/common/items/armor/hand/steel_0.ron +++ b/assets/common/items/armor/hand/steel_0.ron @@ -1,8 +1,12 @@ Item( name: "Steel Gauntlets", - description: "Gauntlets made of steel", + description: "Gauntlets forged from steel.", kind: Armor( - kind: Hand(Steel0), - stats: (20), + ( + kind: Hand(Steel0), + stats: ( + protection: Normal(10.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/twig.ron b/assets/common/items/armor/hand/twig.ron index bf61fe0209..9cb73a911b 100644 --- a/assets/common/items/armor/hand/twig.ron +++ b/assets/common/items/armor/hand/twig.ron @@ -1,8 +1,12 @@ Item( name: "Twig Wraps", - description: "Handwraps woven from twigs.\n\n", + description: "Handwraps woven from twigs.", kind: Armor( - kind: Hand(Twig), - stats: (20), + ( + kind: Hand(Twig), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/twigsflowers.ron b/assets/common/items/armor/hand/twigsflowers.ron index 68cbc953aa..04a6f4b098 100644 --- a/assets/common/items/armor/hand/twigsflowers.ron +++ b/assets/common/items/armor/hand/twigsflowers.ron @@ -1,8 +1,12 @@ Item( name: "Flowery Wraps", - description: "Handwraps woven from twigs and flowers.\n\n", + description: "Handwraps woven from twigs and flowers.", kind: Armor( - kind: Hand(Twigsflowers), - stats: (20), + ( + kind: Hand(Twigsflowers), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/hand/twigsleaves.ron b/assets/common/items/armor/hand/twigsleaves.ron index 13337ae564..8226c3d103 100644 --- a/assets/common/items/armor/hand/twigsleaves.ron +++ b/assets/common/items/armor/hand/twigsleaves.ron @@ -1,8 +1,12 @@ Item( name: "Leafy Wraps", - description: "Handwraps woven from twigs and leaves.\n\n", + description: "Handwraps woven from twigs and leaves.", kind: Armor( - kind: Hand(Twigsleaves), - stats: (20), + ( + kind: Hand(Twigsleaves), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/head/assa_mask_0.ron b/assets/common/items/armor/head/assa_mask_0.ron index 236ea82ef3..2ee38ca25e 100644 --- a/assets/common/items/armor/head/assa_mask_0.ron +++ b/assets/common/items/armor/head/assa_mask_0.ron @@ -1,8 +1,12 @@ Item( name: "Dark Assassin Mask", - description: "Head\n\nArmor: 0\n\n", + description: "Used to obscure your face.", kind: Armor( - kind: Head(AssaMask0), - stats: (20), + ( + kind: Head(AssaMask0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/head/leather_0.ron b/assets/common/items/armor/head/leather_0.ron index c568860917..9ce5ba6814 100644 --- a/assets/common/items/armor/head/leather_0.ron +++ b/assets/common/items/armor/head/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Swift Leather Cap", - description: "Head\n\nArmor: 0\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Head(Leather0), - stats: (20), + ( + kind: Head(Leather0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/neck/neck_0.ron b/assets/common/items/armor/neck/neck_0.ron index d170113336..e8b25c6f58 100644 --- a/assets/common/items/armor/neck/neck_0.ron +++ b/assets/common/items/armor/neck/neck_0.ron @@ -1,8 +1,12 @@ Item( name: "Plain Necklace", - description: "Neck\n\nArmor: 0\n\n", + description: "It's become tarnished with age.", kind: Armor( - kind: Neck(Neck0), - stats: (20), + ( + kind: Neck(Neck0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/assassin.ron b/assets/common/items/armor/pants/assassin.ron index 3fce548e9a..be00f18aa6 100644 --- a/assets/common/items/armor/pants/assassin.ron +++ b/assets/common/items/armor/pants/assassin.ron @@ -1,8 +1,12 @@ Item( name: "Assassin Pants", - description: "Legs\n\nArmor: 0\n\nOnly the best for a member of the creed.\n\n", + description: "Only the best for a member of the creed.", kind: Armor( - kind: Pants(Assassin), - stats: (20), + ( + kind: Pants(Assassin), + stats: ( + protection: Normal(5.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/cloth_blue_0.ron b/assets/common/items/armor/pants/cloth_blue_0.ron index 2b0dc7e163..52ebc0c3a5 100644 --- a/assets/common/items/armor/pants/cloth_blue_0.ron +++ b/assets/common/items/armor/pants/cloth_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Linen Skirt", - description: "Legs\n\nArmor: 0\n\n", + description: "A skirt made from linen.", kind: Armor( - kind: Pants(ClothBlue0), - stats: (20), + ( + kind: Pants(ClothBlue0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/cloth_green_0.ron b/assets/common/items/armor/pants/cloth_green_0.ron index b184717ccf..364e058cee 100644 --- a/assets/common/items/armor/pants/cloth_green_0.ron +++ b/assets/common/items/armor/pants/cloth_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Linen Skirt", - description: "Legs\n\nArmor: 0\n\n", + description: "A skirt made from linen.", kind: Armor( - kind: Pants(ClothGreen0), - stats: (20), + ( + kind: Pants(ClothGreen0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/cloth_purple_0.ron b/assets/common/items/armor/pants/cloth_purple_0.ron index f37662b7db..efcd70f574 100644 --- a/assets/common/items/armor/pants/cloth_purple_0.ron +++ b/assets/common/items/armor/pants/cloth_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Linen Skirt", - description: "Legs\n\nArmor: 0\n\n", + description: "A skirt made from linen.", kind: Armor( - kind: Pants(ClothPurple0), - stats: (20), + ( + kind: Pants(ClothPurple0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/cultist_legs_blue.ron b/assets/common/items/armor/pants/cultist_legs_blue.ron index 5ad8a8c25a..aabf7b54a6 100644 --- a/assets/common/items/armor/pants/cultist_legs_blue.ron +++ b/assets/common/items/armor/pants/cultist_legs_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Skirt", - description: "Legs\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Pants(CultistBlue), - stats: (20), + ( + kind: Pants(CultistBlue), + stats: ( + protection: Normal(24.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/cultist_legs_purple.ron b/assets/common/items/armor/pants/cultist_legs_purple.ron index 39550047d6..a2609e55c9 100644 --- a/assets/common/items/armor/pants/cultist_legs_purple.ron +++ b/assets/common/items/armor/pants/cultist_legs_purple.ron @@ -1,8 +1,12 @@ Item( name: "Purple Cultist Skirt", - description: "Legs\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Pants(CultistPurple), - stats: (20), + ( + kind: Pants(CultistPurple), + stats: ( + protection: Normal(24.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/druid.ron b/assets/common/items/armor/pants/druid.ron index d4b635d4ff..f1e35bac41 100644 --- a/assets/common/items/armor/pants/druid.ron +++ b/assets/common/items/armor/pants/druid.ron @@ -1,8 +1,12 @@ Item( name: "Druid's Kilt", - description: "Feel the breeze!\n\n", + description: "Feel the breeze!", kind: Armor( - kind: Pants(Druid), - stats: (20), + ( + kind: Pants(Druid), + stats: ( + protection: Normal(4.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/hunting.ron b/assets/common/items/armor/pants/hunting.ron index bd30d1801f..16dbf0d1c8 100644 --- a/assets/common/items/armor/pants/hunting.ron +++ b/assets/common/items/armor/pants/hunting.ron @@ -1,8 +1,12 @@ Item( name: "Hunting Pants", - description: "Crafted from soft, supple leather\n\n", + description: "Crafted from soft, supple leather.", kind: Armor( - kind: Pants(Hunting), - stats: (20), + ( + kind: Pants(Hunting), + stats: ( + protection: Normal(8.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/leather_0.ron b/assets/common/items/armor/pants/leather_0.ron index 8eb99060d7..cf7b058a02 100644 --- a/assets/common/items/armor/pants/leather_0.ron +++ b/assets/common/items/armor/pants/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Swift Pants", - description: "Legs\n\nArmor: 0\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Pants(Leather0), - stats: (20), + ( + kind: Pants(Leather0), + stats: ( + protection: Normal(8.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/leather_2.ron b/assets/common/items/armor/pants/leather_2.ron index 31b55c6f62..05d4fcf9df 100644 --- a/assets/common/items/armor/pants/leather_2.ron +++ b/assets/common/items/armor/pants/leather_2.ron @@ -1,8 +1,12 @@ Item( name: "Leather Leg Armour", - description: "Leg armour made of simple leather", + description: "Leg armour made of simple leather.", kind: Armor( - kind: Pants(Leather2), - stats: (20), + ( + kind: Pants(Leather2), + stats: ( + protection: Normal(8.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/plate_green_0.ron b/assets/common/items/armor/pants/plate_green_0.ron index 3194cac534..d1167a6934 100644 --- a/assets/common/items/armor/pants/plate_green_0.ron +++ b/assets/common/items/armor/pants/plate_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron Legguards", - description: "Legs\n\nArmor: 0\n\n", + description: "Greaves forged from iron.", kind: Armor( - kind: Pants(PlateGreen0), - stats: (20), + ( + kind: Pants(PlateGreen0), + stats: ( + protection: Normal(16.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/steel_0.ron b/assets/common/items/armor/pants/steel_0.ron index aed385a94d..7349351884 100644 --- a/assets/common/items/armor/pants/steel_0.ron +++ b/assets/common/items/armor/pants/steel_0.ron @@ -1,8 +1,12 @@ Item( name: "Steel Chausses", - description: "Leg armour made of steel plates", + description: "Greaves forged from steel.", kind: Armor( - kind: Pants(Steel0), - stats: (20), + ( + kind: Pants(Steel0), + stats: ( + protection: Normal(20.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/twig.ron b/assets/common/items/armor/pants/twig.ron index 0cbb923d0e..8dfa916027 100644 --- a/assets/common/items/armor/pants/twig.ron +++ b/assets/common/items/armor/pants/twig.ron @@ -1,8 +1,12 @@ Item( name: "Twig Pants", - description: "Pants woven from twigs. Chafey!\n\n", + description: "Pants woven from twigs. Chafey!", kind: Armor( - kind: Pants(Twig), - stats: (20), + ( + kind: Pants(Twig), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/twigsflowers.ron b/assets/common/items/armor/pants/twigsflowers.ron index abed6274eb..2af4112492 100644 --- a/assets/common/items/armor/pants/twigsflowers.ron +++ b/assets/common/items/armor/pants/twigsflowers.ron @@ -1,8 +1,12 @@ Item( name: "Flowery Pants", - description: "Pants woven from twigs and flowers. Fragrant!\n\n", + description: "Pants woven from twigs and flowers. Fragrant!", kind: Armor( - kind: Pants(Twigsflowers), - stats: (20), + ( + kind: Pants(Twigsflowers), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/twigsleaves.ron b/assets/common/items/armor/pants/twigsleaves.ron index 803993b8d0..14065ef14c 100644 --- a/assets/common/items/armor/pants/twigsleaves.ron +++ b/assets/common/items/armor/pants/twigsleaves.ron @@ -1,8 +1,12 @@ Item( name: "Leafy Pants", - description: "Pants woven from twigs and leaves. Slightly less chafey than the twig pants!\n\n", + description: "Pants woven from twigs and leaves. Slightly less chafey than the twig pants!", kind: Armor( - kind: Pants(Twigsleaves), - stats: (20), + ( + kind: Pants(Twigsleaves), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/pants/worker_blue_0.ron b/assets/common/items/armor/pants/worker_blue_0.ron index 8fc56c4ee6..bd9f2a7916 100644 --- a/assets/common/items/armor/pants/worker_blue_0.ron +++ b/assets/common/items/armor/pants/worker_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Worker Pants", - description: "Legs\n\nArmor: 0\n\n", + description: "Pants used by a farmer, until recently.", kind: Armor( - kind: Pants(WorkerBlue0), - stats: (20), + ( + kind: Pants(WorkerBlue0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/ring/ring_0.ron b/assets/common/items/armor/ring/ring_0.ron index d0448912f3..daa0914bbd 100644 --- a/assets/common/items/armor/ring/ring_0.ron +++ b/assets/common/items/armor/ring/ring_0.ron @@ -1,8 +1,12 @@ Item( name: "Scratched Ring", - description: "Ring\n\nBarely fits your finger.\n\n", + description: "Barely fits your finger.", kind: Armor( - kind: Ring(Ring0), - stats: (20), + ( + kind: Ring(Ring0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/assassin.ron b/assets/common/items/armor/shoulder/assassin.ron index bff3ac8131..a3dbee15aa 100644 --- a/assets/common/items/armor/shoulder/assassin.ron +++ b/assets/common/items/armor/shoulder/assassin.ron @@ -1,8 +1,12 @@ Item( name: "Assassin Shoulder Guard", - description: "Shoulders\n\nArmor: 0\n\nOnly the best for a member of the creed.\n\n", + description: "Only the best for a member of the creed.", kind: Armor( - kind: Shoulder(Assassin), - stats: (20), + ( + kind: Shoulder(Assassin), + stats: ( + protection: Normal(3.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/cloth_blue_0.ron b/assets/common/items/armor/shoulder/cloth_blue_0.ron index 7c19790bd8..432ae4af55 100644 --- a/assets/common/items/armor/shoulder/cloth_blue_0.ron +++ b/assets/common/items/armor/shoulder/cloth_blue_0.ron @@ -1,8 +1,12 @@ Item( name: "Blue Linen Coat", - description: "Shoulders\n\nArmor: 0\n\n", + description: "A warm coat.", kind: Armor( - kind: Shoulder(ClothBlue0), - stats: (20), + ( + kind: Shoulder(ClothBlue0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/cloth_blue_1.ron b/assets/common/items/armor/shoulder/cloth_blue_1.ron index c63a33dbd3..d435fc8ebf 100644 --- a/assets/common/items/armor/shoulder/cloth_blue_1.ron +++ b/assets/common/items/armor/shoulder/cloth_blue_1.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cloth Pads", - description: "Simple shoulderpads made from blue cloth.\n\n", + description: "Simple shoulderpads made from blue cloth.", kind: Armor( - kind: Shoulder(ClothBlue1), - stats: (20), + ( + kind: Shoulder(ClothBlue1), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/cloth_green_0.ron b/assets/common/items/armor/shoulder/cloth_green_0.ron index b3362a8d82..d9eaa35359 100644 --- a/assets/common/items/armor/shoulder/cloth_green_0.ron +++ b/assets/common/items/armor/shoulder/cloth_green_0.ron @@ -1,8 +1,12 @@ Item( name: "Green Linen Coat", - description: "Shoulders\n\nArmor: 0\n\n", + description: "A warm coat.", kind: Armor( - kind: Shoulder(ClothGreen0), - stats: (20), + ( + kind: Shoulder(ClothGreen0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/cloth_purple_0.ron b/assets/common/items/armor/shoulder/cloth_purple_0.ron index ecf434a9b3..10a73ef105 100644 --- a/assets/common/items/armor/shoulder/cloth_purple_0.ron +++ b/assets/common/items/armor/shoulder/cloth_purple_0.ron @@ -1,8 +1,12 @@ Item( name: "Purple Linen Coat", - description: "Shoulders\n\nArmor: 0\n\n", + description: "A warm coat.", kind: Armor( - kind: Shoulder(ClothPurple0), - stats: (20), + ( + kind: Shoulder(ClothPurple0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/cultist_shoulder_blue.ron b/assets/common/items/armor/shoulder/cultist_shoulder_blue.ron index 1e4d3965c8..aa1125a7ac 100644 --- a/assets/common/items/armor/shoulder/cultist_shoulder_blue.ron +++ b/assets/common/items/armor/shoulder/cultist_shoulder_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Mantle", - description: "Shoulders\n\nArmor: 0\n\nA strong shoulder to lean on.\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Shoulder(CultistBlue), - stats: (20), + ( + kind: Shoulder(CultistBlue), + stats: ( + protection: Normal(18.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/cultist_shoulder_purple.ron b/assets/common/items/armor/shoulder/cultist_shoulder_purple.ron index 095d218bf3..52953b9bdb 100644 --- a/assets/common/items/armor/shoulder/cultist_shoulder_purple.ron +++ b/assets/common/items/armor/shoulder/cultist_shoulder_purple.ron @@ -1,8 +1,12 @@ Item( name: "Purple Cultist Mantle", - description: "Shoulders\n\nArmor: 0\n\nA strong shoulder to lean on.\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Shoulder(CultistPurple), - stats: (20), + ( + kind: Shoulder(CultistPurple), + stats: ( + protection: Normal(18.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/druidshoulder.ron b/assets/common/items/armor/shoulder/druidshoulder.ron index b17c658c28..7f88373304 100644 --- a/assets/common/items/armor/shoulder/druidshoulder.ron +++ b/assets/common/items/armor/shoulder/druidshoulder.ron @@ -1,8 +1,12 @@ Item( name: "Druid Shoulders", - description: "Forged for protectors of the wild.\n\n", + description: "Forged for protectors of the wild.", kind: Armor( - kind: Shoulder(DruidShoulder), - stats: (20), + ( + kind: Shoulder(DruidShoulder), + stats: ( + protection: Normal(3.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/iron_spikes.ron b/assets/common/items/armor/shoulder/iron_spikes.ron index a2c95c9360..a253e7a276 100644 --- a/assets/common/items/armor/shoulder/iron_spikes.ron +++ b/assets/common/items/armor/shoulder/iron_spikes.ron @@ -1,8 +1,12 @@ Item( name: "Iron Spiked Pauldrons", - description: "Iron shoulder pads with spikes attached.\n\n", + description: "Iron shoulder pads with spikes attached.", kind: Armor( - kind: Shoulder(IronSpikes), - stats: (20), + ( + kind: Shoulder(IronSpikes), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_0.ron b/assets/common/items/armor/shoulder/leather_0.ron index 82e18d2d45..bbdc133063 100644 --- a/assets/common/items/armor/shoulder/leather_0.ron +++ b/assets/common/items/armor/shoulder/leather_0.ron @@ -1,8 +1,12 @@ Item( name: "Leather Pauldrons", - description: "Shoulders\n\nArmor: 0\n\n", + description: "Shoulder pads made of leather.", kind: Armor( - kind: Shoulder(Leather0), - stats: (20), + ( + kind: Shoulder(Leather0), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_1.ron b/assets/common/items/armor/shoulder/leather_1.ron index 1237ac242c..92c9047c26 100644 --- a/assets/common/items/armor/shoulder/leather_1.ron +++ b/assets/common/items/armor/shoulder/leather_1.ron @@ -1,8 +1,12 @@ Item( name: "Swift Shoulderpads", - description: "Shoulders\n\nArmor: 0\n\nSwift like the wind.\n\n", + description: "Swift like the wind.", kind: Armor( - kind: Shoulder(Leather1), - stats: (20), + ( + kind: Shoulder(Leather1), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_2.ron b/assets/common/items/armor/shoulder/leather_2.ron index 883e07d95d..2a8531895e 100644 --- a/assets/common/items/armor/shoulder/leather_2.ron +++ b/assets/common/items/armor/shoulder/leather_2.ron @@ -1,8 +1,12 @@ Item( name: "Leather Shoulder Pad", - description: "A simple shoulder pad made of leather", + description: "A simple shoulder pad made of leather.", kind: Armor( - kind: Shoulder(Leather2), - stats: (20), + ( + kind: Shoulder(Leather2), + stats: ( + protection: Normal(6.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_iron_0.ron b/assets/common/items/armor/shoulder/leather_iron_0.ron index f19799ca90..6a26e9c7c5 100644 --- a/assets/common/items/armor/shoulder/leather_iron_0.ron +++ b/assets/common/items/armor/shoulder/leather_iron_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron and Leather Spaulders", - description: "Robust spaulders made from iron and leather.\n\n", + description: "Robust spaulders made from iron and leather.", kind: Armor( - kind: Shoulder(IronLeather0), - stats: (20), + ( + kind: Shoulder(IronLeather0), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_iron_1.ron b/assets/common/items/armor/shoulder/leather_iron_1.ron index aaac593da4..2f2bc043fa 100644 --- a/assets/common/items/armor/shoulder/leather_iron_1.ron +++ b/assets/common/items/armor/shoulder/leather_iron_1.ron @@ -1,8 +1,12 @@ Item( name: "Iron and Leather Spaulders", - description: "Robust spaulders made from iron and leather.\n\n", + description: "Robust spaulders made from iron and leather.", kind: Armor( - kind: Shoulder(IronLeather1), - stats: (20), + ( + kind: Shoulder(IronLeather1), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_iron_2.ron b/assets/common/items/armor/shoulder/leather_iron_2.ron index c939e0e0e7..653597c82e 100644 --- a/assets/common/items/armor/shoulder/leather_iron_2.ron +++ b/assets/common/items/armor/shoulder/leather_iron_2.ron @@ -1,8 +1,12 @@ Item( name: "Iron and Leather Spaulders", - description: "Robust spaulders made from iron and leather.\n\n", + description: "Robust spaulders made from iron and leather.", kind: Armor( - kind: Shoulder(IronLeather2), - stats: (20), + ( + kind: Shoulder(IronLeather2), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_iron_3.ron b/assets/common/items/armor/shoulder/leather_iron_3.ron index 2c30a2452a..fb0eb45951 100644 --- a/assets/common/items/armor/shoulder/leather_iron_3.ron +++ b/assets/common/items/armor/shoulder/leather_iron_3.ron @@ -1,8 +1,12 @@ Item( name: "Iron and Leather Spaulders", - description: "Robust spaulders made from iron and leather.\n\n", + description: "Robust spaulders made from iron and leather.", kind: Armor( - kind: Shoulder(IronLeather3), - stats: (20), + ( + kind: Shoulder(IronLeather3), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/leather_strips.ron b/assets/common/items/armor/shoulder/leather_strips.ron index e8a84127f8..fa0e176e2b 100644 --- a/assets/common/items/armor/shoulder/leather_strips.ron +++ b/assets/common/items/armor/shoulder/leather_strips.ron @@ -1,8 +1,12 @@ Item( name: "Leather Strips", - description: "Shoulder wraps made from leather strips.\n\n", + description: "Shoulder wraps made from leather strips.", kind: Armor( - kind: Shoulder(LeatherStrips), - stats: (20), + ( + kind: Shoulder(LeatherStrips), + stats: ( + protection: Normal(4.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/plate_0.ron b/assets/common/items/armor/shoulder/plate_0.ron index f6d3cc8590..54c7fa4dbf 100644 --- a/assets/common/items/armor/shoulder/plate_0.ron +++ b/assets/common/items/armor/shoulder/plate_0.ron @@ -1,8 +1,12 @@ Item( name: "Iron Shoulderguards", - description: "Shoulders\n\nArmor: 0\n\nA strong shoulder to lean on.\n\n", + description: "Shoulderguards forged from iron.", kind: Armor( - kind: Shoulder(Plate0), - stats: (20), + ( + kind: Shoulder(Plate0), + stats: ( + protection: Normal(12.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/steel_0.ron b/assets/common/items/armor/shoulder/steel_0.ron index 6c48846f9a..0ccd4a907a 100644 --- a/assets/common/items/armor/shoulder/steel_0.ron +++ b/assets/common/items/armor/shoulder/steel_0.ron @@ -1,8 +1,12 @@ Item( name: "Steel Shoulder Pad", - description: "A simple shoulder pad made of steel", + description: "A simple shoulder pad made of steel.", kind: Armor( - kind: Shoulder(Steel0), - stats: (20), + ( + kind: Shoulder(Steel0), + stats: ( + protection: Normal(15.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/twigs.ron b/assets/common/items/armor/shoulder/twigs.ron index 144d5552b0..65995c6699 100644 --- a/assets/common/items/armor/shoulder/twigs.ron +++ b/assets/common/items/armor/shoulder/twigs.ron @@ -1,8 +1,12 @@ Item( name: "Twiggy Shoulders", - description: "Spaulders made from tightly tied twigs.\n\n", + description: "Spaulders made from tightly tied twigs.", kind: Armor( - kind: Shoulder(TwiggyShoulder), - stats: (20), + ( + kind: Shoulder(TwiggyShoulder), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/twigsflowers.ron b/assets/common/items/armor/shoulder/twigsflowers.ron index d62e5d3e10..e8f2fecfe2 100644 --- a/assets/common/items/armor/shoulder/twigsflowers.ron +++ b/assets/common/items/armor/shoulder/twigsflowers.ron @@ -1,8 +1,12 @@ Item( name: "Flowery Shoulders", - description: "Flowery, leafy spaulders.\n\n", + description: "Flowery, leafy spaulders.", kind: Armor( - kind: Shoulder(FlowerShoulder), - stats: (20), + ( + kind: Shoulder(FlowerShoulder), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/shoulder/twigsleaves.ron b/assets/common/items/armor/shoulder/twigsleaves.ron index dc88f790e2..9e87f878f5 100644 --- a/assets/common/items/armor/shoulder/twigsleaves.ron +++ b/assets/common/items/armor/shoulder/twigsleaves.ron @@ -1,8 +1,12 @@ Item( name: "Leafy Shoulders", - description: "Spaulders made from tied twigs and leaves.\n\n", + description: "Spaulders made from tied twigs and leaves.", kind: Armor( - kind: Shoulder(LeafyShoulder), - stats: (20), + ( + kind: Shoulder(LeafyShoulder), + stats: ( + protection: Normal(9.0), + ), + ) ), ) diff --git a/assets/common/items/armor/starter/rugged_chest.ron b/assets/common/items/armor/starter/rugged_chest.ron index d559dd6dec..2ad170a8ae 100644 --- a/assets/common/items/armor/starter/rugged_chest.ron +++ b/assets/common/items/armor/starter/rugged_chest.ron @@ -1,8 +1,12 @@ Item( name: "Rugged Shirt", - description: "Chest\n\nArmor: 0\n\nSmells like Adventure.\n\n", + description: "Smells like Adventure.", kind: Armor( - kind: Chest(Rugged0), - stats: (20), + ( + kind: Chest(Rugged0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/starter/rugged_pants.ron b/assets/common/items/armor/starter/rugged_pants.ron index 0444aeb665..3b46eb61ba 100644 --- a/assets/common/items/armor/starter/rugged_pants.ron +++ b/assets/common/items/armor/starter/rugged_pants.ron @@ -1,8 +1,12 @@ Item( name: "Rugged Commoner's Pants", - description: "Legs\n\nArmor: 0\n\nThey remind you of the old days.\n\n", + description: "They remind you of the old days.", kind: Armor( - kind: Pants(Rugged0), - stats: (20), + ( + kind: Pants(Rugged0), + stats: ( + protection: Normal(1.0), + ), + ) ), ) diff --git a/assets/common/items/armor/starter/sandals_0.ron b/assets/common/items/armor/starter/sandals_0.ron index 3d2201aa19..cbfd3b4805 100644 --- a/assets/common/items/armor/starter/sandals_0.ron +++ b/assets/common/items/armor/starter/sandals_0.ron @@ -1,8 +1,12 @@ Item( name: "Worn out Sandals", - description: "Feet\n\nArmor: 0\n\nLoyal companions.\n\n", + description: "Loyal companions.", kind: Armor( - kind: Foot(Sandal0), - stats: (20), + ( + kind: Foot(Sandal0), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/armor/tabard/admin.ron b/assets/common/items/armor/tabard/admin.ron index 4ad17edb50..665541e195 100644 --- a/assets/common/items/armor/tabard/admin.ron +++ b/assets/common/items/armor/tabard/admin.ron @@ -1,8 +1,12 @@ Item( name: "Admin's Tabard", - description: "Tabard\n\nWith great power comes\ngreat responsibility.\n\n", + description: "With great power comes\ngreat responsibility.", kind: Armor( - kind: Tabard(Admin), - stats: (20), + ( + kind: Tabard(Admin), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/potion_big.ron b/assets/common/items/consumable/potion_big.ron similarity index 85% rename from assets/common/items/potion_big.ron rename to assets/common/items/consumable/potion_big.ron index 42553d58e2..60ddf64298 100644 --- a/assets/common/items/potion_big.ron +++ b/assets/common/items/consumable/potion_big.ron @@ -2,11 +2,10 @@ Item( name: "Large Potion", description: "Restores 100 Health\n\n", kind: Consumable( - kind: PotionMinor, + kind: PotionLarge, effect: Health(( amount: 100, cause: Item, )), - , ), ) diff --git a/assets/common/items/potion_med.ron b/assets/common/items/consumable/potion_med.ron similarity index 89% rename from assets/common/items/potion_med.ron rename to assets/common/items/consumable/potion_med.ron index 61cf612204..ece9348e1b 100644 --- a/assets/common/items/potion_med.ron +++ b/assets/common/items/consumable/potion_med.ron @@ -2,7 +2,7 @@ Item( name: "Medium Potion", description: "Restores 70 Health\n\n", kind: Consumable( - kind: PotionMinor, + kind: PotionMed, effect: Health(( amount: 70, cause: Item, diff --git a/assets/common/items/potion_minor.ron b/assets/common/items/consumable/potion_minor.ron similarity index 100% rename from assets/common/items/potion_minor.ron rename to assets/common/items/consumable/potion_minor.ron diff --git a/assets/common/items/crafting_ing/empty_vial.ron b/assets/common/items/crafting_ing/empty_vial.ron new file mode 100644 index 0000000000..422a2870dd --- /dev/null +++ b/assets/common/items/crafting_ing/empty_vial.ron @@ -0,0 +1,7 @@ +Item( + name: "Empty Vial", + description: "Can be filled with fluids.", + kind: Ingredient( + kind: EmptyVial, + ) +) diff --git a/assets/common/items/crafting_ing/leather_scraps.ron b/assets/common/items/crafting_ing/leather_scraps.ron new file mode 100644 index 0000000000..cc546f8639 --- /dev/null +++ b/assets/common/items/crafting_ing/leather_scraps.ron @@ -0,0 +1,7 @@ +Item( + name: "Leather Scraps", + description: "Used to craft various items.", + kind: Ingredient( + kind: LeatherScraps, + ) +) diff --git a/assets/common/items/crafting_ing/shiny_gem.ron b/assets/common/items/crafting_ing/shiny_gem.ron new file mode 100644 index 0000000000..ec396047fa --- /dev/null +++ b/assets/common/items/crafting_ing/shiny_gem.ron @@ -0,0 +1,7 @@ +Item( + name: "Shiny Gem", + description: "It's so shiny!", + kind: Ingredient( + kind: ShinyGem, + ) +) diff --git a/assets/common/items/crafting_ing/stones.ron b/assets/common/items/crafting_ing/stones.ron new file mode 100644 index 0000000000..20d5842255 --- /dev/null +++ b/assets/common/items/crafting_ing/stones.ron @@ -0,0 +1,7 @@ +Item( + name: "Stones", + description: "Pebbles from the ground.", + kind: Ingredient( + kind: Stones, + ) +) diff --git a/assets/common/items/crafting_ing/twigs.ron b/assets/common/items/crafting_ing/twigs.ron new file mode 100644 index 0000000000..3eaba0f24a --- /dev/null +++ b/assets/common/items/crafting_ing/twigs.ron @@ -0,0 +1,7 @@ +Item( + name: "Twigs", + description: "Dry.", + kind: Ingredient( + kind: Twigs, + ) +) diff --git a/assets/common/items/crafting_tools/craftsman_hammer.ron b/assets/common/items/crafting_tools/craftsman_hammer.ron new file mode 100644 index 0000000000..30bdb55506 --- /dev/null +++ b/assets/common/items/crafting_tools/craftsman_hammer.ron @@ -0,0 +1,7 @@ +Item( + name: "Craftsman Hammer", + description: "Used to craft various items.", + kind: Ingredient( + kind: CraftsmanHammer, + ) +) diff --git a/assets/common/items/crafting_tools/mortar_pestle.ron b/assets/common/items/crafting_tools/mortar_pestle.ron new file mode 100644 index 0000000000..d983f15b3f --- /dev/null +++ b/assets/common/items/crafting_tools/mortar_pestle.ron @@ -0,0 +1,7 @@ +Item( + name: "Mortar and Pestle", + description: "Crushes and grinds things into\na fine powder or paste.\nUsed to craft various items.", + kind: Ingredient( + kind: MortarPestle, + ) +) diff --git a/assets/common/items/debug/admin.ron b/assets/common/items/debug/admin.ron index d1741be6b5..433e9c0ecc 100644 --- a/assets/common/items/debug/admin.ron +++ b/assets/common/items/debug/admin.ron @@ -1,8 +1,12 @@ Item( name: "Admin's Tabard", - description: "Tabard\n\nWith great power comes\ngreat responsibility. ", + description: "With great power comes\ngreat responsibility.", kind: Armor( - kind: Tabard(Admin), - stats: (20), + ( + kind: Tabard(Admin), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/admin_back.ron b/assets/common/items/debug/admin_back.ron index dbdfed10f3..d491343bbf 100644 --- a/assets/common/items/debug/admin_back.ron +++ b/assets/common/items/debug/admin_back.ron @@ -1,8 +1,12 @@ Item( name: "Admin's Cape", - description: "Back\n\nArmor: 0\n\nWith great power comes\ngreat responsibility. ", + description: "With great power comes\ngreat responsibility.", kind: Armor( - kind: Back(Admin), - stats: (20), + ( + kind: Back(Admin), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/cultist_belt.ron b/assets/common/items/debug/cultist_belt.ron index 1b2765d2c8..648c2a5adb 100644 --- a/assets/common/items/debug/cultist_belt.ron +++ b/assets/common/items/debug/cultist_belt.ron @@ -1,8 +1,12 @@ Item( name: "Cultist Belt", - description: "Belt\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Belt(Cultist), - stats: (20), + ( + kind: Belt(Cultist), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/cultist_boots.ron b/assets/common/items/debug/cultist_boots.ron index 70d134e5fe..5a130430be 100644 --- a/assets/common/items/debug/cultist_boots.ron +++ b/assets/common/items/debug/cultist_boots.ron @@ -1,8 +1,12 @@ Item( name: "Cultist Boots", - description: "Feet\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Foot(Cultist), - stats: (20), + ( + kind: Foot(Cultist), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/cultist_chest_blue.ron b/assets/common/items/debug/cultist_chest_blue.ron index 2039ea6d76..7ae1a9f3ab 100644 --- a/assets/common/items/debug/cultist_chest_blue.ron +++ b/assets/common/items/debug/cultist_chest_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Chest", - description: "Chest\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Chest(CultistBlue), - stats: (20), + ( + kind: Chest(CultistBlue), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/cultist_hands_blue.ron b/assets/common/items/debug/cultist_hands_blue.ron index c6d3076b58..e9955b2a23 100644 --- a/assets/common/items/debug/cultist_hands_blue.ron +++ b/assets/common/items/debug/cultist_hands_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Gloves", - description: "Hands\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Hand(CultistBlue), - stats: (20), + ( + kind: Hand(CultistBlue), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/cultist_legs_blue.ron b/assets/common/items/debug/cultist_legs_blue.ron index 5ad8a8c25a..4ab7fa5119 100644 --- a/assets/common/items/debug/cultist_legs_blue.ron +++ b/assets/common/items/debug/cultist_legs_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Skirt", - description: "Legs\n\nArmor: 0\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Pants(CultistBlue), - stats: (20), + ( + kind: Pants(CultistBlue), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/debug/cultist_shoulder_blue.ron b/assets/common/items/debug/cultist_shoulder_blue.ron index 1e4d3965c8..92c1ff5092 100644 --- a/assets/common/items/debug/cultist_shoulder_blue.ron +++ b/assets/common/items/debug/cultist_shoulder_blue.ron @@ -1,8 +1,12 @@ Item( name: "Blue Cultist Mantle", - description: "Shoulders\n\nArmor: 0\n\nA strong shoulder to lean on.\n\n", + description: "Ceremonial attire used by members.", kind: Armor( - kind: Shoulder(CultistBlue), - stats: (20), + ( + kind: Shoulder(CultistBlue), + stats: ( + protection: Invincible, + ), + ) ), ) diff --git a/assets/common/items/apple.ron b/assets/common/items/food/apple.ron similarity index 100% rename from assets/common/items/apple.ron rename to assets/common/items/food/apple.ron diff --git a/assets/common/items/food/apple_mushroom_curry.ron b/assets/common/items/food/apple_mushroom_curry.ron new file mode 100644 index 0000000000..d8146e4c82 --- /dev/null +++ b/assets/common/items/food/apple_mushroom_curry.ron @@ -0,0 +1,11 @@ +Item( + name: "Mushroom Curry", + description: "Restores 120 Health\n\nWho could say no to that?\n\n", + kind: Consumable( + kind: AppleShroomCurry, + effect: Health(( + amount: 120, + cause: Item, + )), + ), +) diff --git a/assets/common/items/food/apple_stick.ron b/assets/common/items/food/apple_stick.ron new file mode 100644 index 0000000000..78bd97ce66 --- /dev/null +++ b/assets/common/items/food/apple_stick.ron @@ -0,0 +1,11 @@ +Item( + name: "Apple Stick", + description: "Restores 60 Health\n\n", + kind: Consumable( + kind: AppleStick, + effect: Health(( + amount: 60, + cause: Item, + )), + ), +) diff --git a/assets/common/items/cheese.ron b/assets/common/items/food/cheese.ron similarity index 100% rename from assets/common/items/cheese.ron rename to assets/common/items/food/cheese.ron diff --git a/assets/common/items/coconut.ron b/assets/common/items/food/coconut.ron similarity index 100% rename from assets/common/items/coconut.ron rename to assets/common/items/food/coconut.ron diff --git a/assets/common/items/mushroom.ron b/assets/common/items/food/mushroom.ron similarity index 100% rename from assets/common/items/mushroom.ron rename to assets/common/items/food/mushroom.ron diff --git a/assets/common/items/food/mushroom_stick.ron b/assets/common/items/food/mushroom_stick.ron new file mode 100644 index 0000000000..d44bbbec48 --- /dev/null +++ b/assets/common/items/food/mushroom_stick.ron @@ -0,0 +1,11 @@ +Item( + name: "Mushroom Stick", + description: "Restores 50 Health\n\n", + kind: Consumable( + kind: MushroomStick, + effect: Health(( + amount: 50, + cause: Item, + )), + ), +) diff --git a/assets/common/items/velorite.ron b/assets/common/items/ore/velorite.ron similarity index 100% rename from assets/common/items/velorite.ron rename to assets/common/items/ore/velorite.ron diff --git a/assets/common/items/veloritefrag.ron b/assets/common/items/ore/veloritefrag.ron similarity index 100% rename from assets/common/items/veloritefrag.ron rename to assets/common/items/ore/veloritefrag.ron diff --git a/assets/common/items/testing/test_boots.ron b/assets/common/items/testing/test_boots.ron index b8a4bfa3cf..405cd4d7c9 100644 --- a/assets/common/items/testing/test_boots.ron +++ b/assets/common/items/testing/test_boots.ron @@ -2,7 +2,11 @@ Item( name: "Testing Boots", description: "Hopefully this test doesn't break!", kind: Armor( - kind: Foot(Dark), - stats: (20), + ( + kind: Foot(Dark), + stats: ( + protection: Normal(0.0), + ), + ) ), ) diff --git a/assets/common/items/bomb.ron b/assets/common/items/utility/bomb.ron similarity index 100% rename from assets/common/items/bomb.ron rename to assets/common/items/utility/bomb.ron diff --git a/assets/common/items/bomb_pile.ron b/assets/common/items/utility/bomb_pile.ron similarity index 100% rename from assets/common/items/bomb_pile.ron rename to assets/common/items/utility/bomb_pile.ron diff --git a/assets/common/items/collar.ron b/assets/common/items/utility/collar.ron similarity index 100% rename from assets/common/items/collar.ron rename to assets/common/items/utility/collar.ron diff --git a/assets/common/items/training_dummy.ron b/assets/common/items/utility/training_dummy.ron similarity index 100% rename from assets/common/items/training_dummy.ron rename to assets/common/items/utility/training_dummy.ron diff --git a/assets/common/loot_table.ron b/assets/common/loot_table.ron index 61f7d939cd..fa609e55c6 100644 --- a/assets/common/loot_table.ron +++ b/assets/common/loot_table.ron @@ -1,18 +1,23 @@ [ // All loot rates go here // food - (3, "common.items.cheese"), - (3, "common.items.apple"), - (3, "common.items.mushroom"), + (3, "common.items.food.cheese"), + (3, "common.items.food.apple"), + (3, "common.items.food.mushroom"), + (1, "common.items.food.coconut"), // miscellaneous - (0.4, "common.items.velorite"), - (0.6, "common.items.veloritefrag"), - (0.6, "common.items.cheese"), - (0.6, "common.items.apple"), - (1.5, "common.items.potion_minor"), - (0.5, "common.items.collar"), - (0.5, "common.items.bomb_pile"), - (1, "common.items.bomb"), + (0.4, "common.items.ore.velorite"), + (0.6, "common.items.ore.veloritefrag"), + (0.1, "common.items.consumable.potion_minor"), + (0.01, "common.items.utility.collar"), + (0.01, "common.items.utility.bomb_pile"), + (0.1, "common.items.utility.bomb"), + // crafting ingredients + (0.5, "common.items.crafting_ing.shiny_gem"), + (2, "common.items.crafting_ing.leather_scraps"), + (1, "common.items.crafting_ing.empty_vial"), + (2, "common.items.crafting_ing.stones"), + (3, "common.items.crafting_ing.twigs"), // swords (0.1, "common.items.weapons.sword.starter_sword"), (0.1, "common.items.weapons.sword.wood_sword"), @@ -54,53 +59,97 @@ // bows (1, "common.items.weapons.bow.starter_bow"), // belts - (0.2, "common.items.armor.belt.cloth_blue_0"), - (0.2, "common.items.armor.belt.cloth_green_0"), - (0.2, "common.items.armor.belt.cloth_purple_0"), - (0.15, "common.items.armor.belt.leather_0"), - (0.15, "common.items.armor.belt.leather_2"), - (0.1, "common.items.armor.belt.steel_0"), - (0.05, "common.items.armor.belt.plate_0"), + (0.17, "common.items.armor.belt.cloth_blue_0"), + (0.17, "common.items.armor.belt.cloth_green_0"), + (0.17, "common.items.armor.belt.cloth_purple_0"), + (0.08, "common.items.armor.belt.druid"), + (0.06, "common.items.armor.belt.leather_0"), + (0.06, "common.items.armor.belt.leather_2"), + (0.02, "common.items.armor.belt.twig"), + (0.02, "common.items.armor.belt.twigsflowers"), + (0.02, "common.items.armor.belt.twigsleaves"), + (0.03, "common.items.armor.belt.plate_0"), + (0.01, "common.items.armor.belt.steel_0"), // chests - (0.2, "common.items.armor.chest.cloth_blue_0"), - (0.2, "common.items.armor.chest.cloth_green_0"), - (0.2, "common.items.armor.chest.cloth_purple_0"), - (0.15, "common.items.armor.chest.leather_0"), - (0.15, "common.items.armor.chest.leather_2"), - (0.1, "common.items.armor.chest.steel_0"), - (0.05, "common.items.armor.chest.plate_green_0"), + (0.08, "common.items.armor.chest.cloth_blue_0"), + (0.08, "common.items.armor.chest.cloth_green_0"), + (0.08, "common.items.armor.chest.cloth_purple_0"), + (0.025, "common.items.armor.chest.worker_green_0"), + (0.025, "common.items.armor.chest.worker_green_1"), + (0.025, "common.items.armor.chest.worker_orange_0"), + (0.025, "common.items.armor.chest.worker_orange_1"), + (0.025, "common.items.armor.chest.worker_purple_0"), + (0.025, "common.items.armor.chest.worker_purple_1"), + (0.025, "common.items.armor.chest.worker_red_0"), + (0.025, "common.items.armor.chest.worker_red_1"), + (0.025, "common.items.armor.chest.worker_yellow_0"), + (0.025, "common.items.armor.chest.worker_yellow_1"), + (0.08, "common.items.armor.chest.druid"), + (0.06, "common.items.armor.chest.leather_0"), + (0.06, "common.items.armor.chest.leather_2"), + (0.02, "common.items.armor.chest.twig"), + (0.02, "common.items.armor.chest.twigsflowers"), + (0.02, "common.items.armor.chest.twigsleaves"), + (0.03, "common.items.armor.chest.plate_green_0"), + (0.01, "common.items.armor.chest.steel_0"), // shoes - (0.2, "common.items.armor.foot.cloth_blue_0"), - (0.2, "common.items.armor.foot.cloth_green_0"), - (0.2, "common.items.armor.foot.cloth_purple_0"), - (0.15, "common.items.armor.foot.leather_0"), - (0.15, "common.items.armor.foot.leather_2"), - (0.1, "common.items.armor.foot.steel_0"), - (0.05, "common.items.armor.foot.plate_0"), + (0.15, "common.items.armor.foot.cloth_blue_0"), + (0.15, "common.items.armor.foot.cloth_green_0"), + (0.15, "common.items.armor.foot.cloth_purple_0"), + (0.08, "common.items.armor.foot.druid"), + (0.06, "common.items.armor.foot.leather_0"), + (0.06, "common.items.armor.foot.leather_2"), + (0.02, "common.items.armor.foot.twig"), + (0.02, "common.items.armor.foot.twigsflowers"), + (0.02, "common.items.armor.foot.twigsleaves"), + (0.03, "common.items.armor.foot.plate_0"), + (0.01, "common.items.armor.foot.steel_0"), // pants - (0.2, "common.items.armor.pants.cloth_blue_0"), - (0.2, "common.items.armor.pants.cloth_green_0"), - (0.2, "common.items.armor.pants.cloth_purple_0"), - (0.15, "common.items.armor.pants.leather_0"), - (0.1, "common.items.armor.pants.steel_0"), - (0.05, "common.items.armor.pants.plate_green_0"), + (0.125, "common.items.armor.pants.cloth_blue_0"), + (0.125, "common.items.armor.pants.cloth_green_0"), + (0.125, "common.items.armor.pants.cloth_purple_0"), + (0.125, "common.items.armor.pants.worker_blue_0"), + (0.08, "common.items.armor.pants.druid"), + (0.04, "common.items.armor.pants.leather_0"), + (0.04, "common.items.armor.pants.leather_2"), + (0.04, "common.items.armor.pants.hunting"), + (0.02, "common.items.armor.pants.twig"), + (0.02, "common.items.armor.pants.twigsflowers"), + (0.02, "common.items.armor.pants.twigsleaves"), + (0.03, "common.items.armor.pants.plate_green_0"), + (0.01, "common.items.armor.pants.steel_0"), // shoulders - (0.2, "common.items.armor.shoulder.cloth_blue_0"), - (0.2, "common.items.armor.shoulder.cloth_green_0"), - (0.2, "common.items.armor.shoulder.cloth_purple_0"), - (0.1, "common.items.armor.shoulder.leather_0"), - (0.1, "common.items.armor.shoulder.leather_1"), - (0.1, "common.items.armor.shoulder.leather_2"), - (0.1, "common.items.armor.shoulder.steel_0"), - (0.05, "common.items.armor.shoulder.plate_0"), + (0.125, "common.items.armor.shoulder.cloth_blue_0"), + (0.125, "common.items.armor.shoulder.cloth_blue_1"), + (0.125, "common.items.armor.shoulder.cloth_green_0"), + (0.125, "common.items.armor.shoulder.cloth_purple_0"), + (0.06, "common.items.armor.shoulder.druidshoulder"), + (0.06, "common.items.armor.shoulder.leather_strips"), + (0.04, "common.items.armor.shoulder.leather_0"), + (0.04, "common.items.armor.shoulder.leather_1"), + (0.04, "common.items.armor.shoulder.leather_2"), + (0.01, "common.items.armor.shoulder.twigs"), + (0.01, "common.items.armor.shoulder.twigsflowers"), + (0.01, "common.items.armor.shoulder.twigsleaves"), + (0.01, "common.items.armor.shoulder.leather_iron_0"), + (0.01, "common.items.armor.shoulder.leather_iron_1"), + (0.01, "common.items.armor.shoulder.leather_iron_2"), + (0.01, "common.items.armor.shoulder.leather_iron_3"), + (0.015, "common.items.armor.shoulder.plate_0"), + (0.015, "common.items.armor.shoulder.iron_spikes"), + (0.01, "common.items.armor.shoulder.steel_0"), //gloves - (0.2, "common.items.armor.hand.cloth_blue_0"), - (0.2, "common.items.armor.hand.cloth_green_0"), - (0.2, "common.items.armor.hand.cloth_purple_0"), - (0.15, "common.items.armor.hand.leather_0"), - (0.15, "common.items.armor.hand.leather_2"), - (0.1, "common.items.armor.hand.steel_0"), - (0.05, "common.items.armor.hand.plate_0"), + (0.17, "common.items.armor.hand.cloth_blue_0"), + (0.17, "common.items.armor.hand.cloth_green_0"), + (0.17, "common.items.armor.hand.cloth_purple_0"), + (0.08, "common.items.armor.hand.druid"), + (0.06, "common.items.armor.hand.leather_0"), + (0.06, "common.items.armor.hand.leather_2"), + (0.02, "common.items.armor.hand.twig"), + (0.02, "common.items.armor.hand.twigsflowers"), + (0.02, "common.items.armor.hand.twigsleaves"), + (0.03, "common.items.armor.hand.plate_0"), + (0.01, "common.items.armor.hand.steel_0"), // rings (0.6, "common.items.armor.ring.ring_0"), // capes diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron new file mode 100644 index 0000000000..cda3378172 --- /dev/null +++ b/assets/common/recipe_book.ron @@ -0,0 +1,12 @@ +{ + "crafting_hammer": (("common.items.crafting_tools.craftsman_hammer", 1),[("common.items.crafting_ing.twigs", 10), ("common.items.crafting_ing.stones", 10)]), + "mortar_pestle": (("common.items.crafting_tools.mortar_pestle", 1), [("common.items.crafting_ing.stones", 6), ("common.items.food.coconut", 2), ("common.items.crafting_tools.craftsman_hammer", 0)]), + "velorite_frag": (("common.items.ore.veloritefrag", 2), [("common.items.ore.velorite", 1), ("common.items.crafting_tools.craftsman_hammer", 0)]), + "potion_s": (("common.items.consumable.potion_minor", 1), [("common.items.crafting_ing.empty_vial", 1), ("common.items.ore.veloritefrag", 2)]), + "potion_m": (("common.items.consumable.potion_med", 1), [("common.items.consumable.potion_minor", 2), ("common.items.ore.veloritefrag", 4)]), + "collar_basic": (("common.items.utility.collar", 1), [("common.items.crafting_ing.leather_scraps", 5), ("common.items.crafting_ing.shiny_gem", 1)]), + "bomb_coconut": (("common.items.utility.bomb", 1), [("common.items.crafting_ing.stones", 10), ("common.items.food.coconut", 2), ("common.items.ore.veloritefrag", 2), ("common.items.crafting_tools.mortar_pestle", 0)]), + "apple_shroom_curry": (("common.items.food.apple_mushroom_curry", 1), [("common.items.food.mushroom", 10), ("common.items.food.coconut", 1), ("common.items.food.apple", 5), ("common.items.crafting_tools.mortar_pestle", 0)]), + "apples_stick": (("common.items.food.apple_stick", 1),[("common.items.crafting_ing.twigs", 1), ("common.items.food.apple", 3)]), + "mushroom_stick": (("common.items.food.mushroom_stick", 1),[("common.items.crafting_ing.twigs", 1), ("common.items.food.mushroom", 5)]), +} diff --git a/assets/voxygen/audio/sfx/crafting/hammer.wav b/assets/voxygen/audio/sfx/crafting/hammer.wav new file mode 100644 index 0000000000..03223c4ee4 --- /dev/null +++ b/assets/voxygen/audio/sfx/crafting/hammer.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be15f45edcf757e9e2135323d5d9d96069022ef21dcff72df80a49637bd31bc3 +size 185678 diff --git a/assets/voxygen/background/bg_8.png b/assets/voxygen/background/bg_8.png index 1688dee237..d429e5eec4 100644 --- a/assets/voxygen/background/bg_8.png +++ b/assets/voxygen/background/bg_8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74e8aab0abd64fe190607c87a6e70a3d3b4409947d8698754ff3c31e0e6debc9 -size 403497 +oid sha256:bd6507de7afa3bbc697fb2867fadc44184298dc9026f30fe91613c06dc72723d +size 906501 diff --git a/assets/voxygen/element/animation/gears/1.png b/assets/voxygen/element/animation/gears/1.png new file mode 100644 index 0000000000..fd1946be6b --- /dev/null +++ b/assets/voxygen/element/animation/gears/1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:798293778955ec9bfe8ae9a2fb6de33adff8a1ba457d693689de644f71bc8f9e +size 843 diff --git a/assets/voxygen/element/animation/gears/2.png b/assets/voxygen/element/animation/gears/2.png new file mode 100644 index 0000000000..d248a4b4cf --- /dev/null +++ b/assets/voxygen/element/animation/gears/2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63da1eb3c7663a921ef7f0995c8a5beee3121fa8cb2d4d3f50d6c5d8ade6ef64 +size 2067 diff --git a/assets/voxygen/element/animation/gears/3.png b/assets/voxygen/element/animation/gears/3.png new file mode 100644 index 0000000000..f4f76e1fea --- /dev/null +++ b/assets/voxygen/element/animation/gears/3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5336ccfb5c8668959f376475dd32c48b5850c527da56a7924140027dd904a921 +size 2066 diff --git a/assets/voxygen/element/animation/gears/4.png b/assets/voxygen/element/animation/gears/4.png new file mode 100644 index 0000000000..b70867e7a0 --- /dev/null +++ b/assets/voxygen/element/animation/gears/4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cb70f05b0e179d1c99f9878148a8a93f7347e8610a74348e5a3d7100f0303d0 +size 2073 diff --git a/assets/voxygen/element/animation/gears/5.png b/assets/voxygen/element/animation/gears/5.png new file mode 100644 index 0000000000..7652f808cb --- /dev/null +++ b/assets/voxygen/element/animation/gears/5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ca60d03c82bc855cfda2e1fb61273f00a355e2c80cc7882c5f79c56c7de67c8 +size 2101 diff --git a/assets/voxygen/element/buttons/anvil.png b/assets/voxygen/element/buttons/anvil.png new file mode 100644 index 0000000000..1782d62f65 --- /dev/null +++ b/assets/voxygen/element/buttons/anvil.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c23cc6edcc9af1e2d82432d9b4f60cbf905cf3d970e6ae467f333cffdcb86e06 +size 355 diff --git a/assets/voxygen/element/buttons/anvil_hover.png b/assets/voxygen/element/buttons/anvil_hover.png new file mode 100644 index 0000000000..e57ba7b1e0 --- /dev/null +++ b/assets/voxygen/element/buttons/anvil_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be5dd7c186a92d519fcbb991308abb911889c5f830028ae4345f190fd7115a99 +size 437 diff --git a/assets/voxygen/element/buttons/anvil_press.png b/assets/voxygen/element/buttons/anvil_press.png new file mode 100644 index 0000000000..075d36e93b --- /dev/null +++ b/assets/voxygen/element/buttons/anvil_press.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9eccb36375e2ddf203f42809783dfd5382ab59a1cec50e16da85b6b03d8552f3 +size 412 diff --git a/assets/voxygen/element/frames/selection.png b/assets/voxygen/element/frames/selection.png new file mode 100644 index 0000000000..24566a3030 --- /dev/null +++ b/assets/voxygen/element/frames/selection.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66bb0ef05d8ba8a73c1c49ea12dcd401d52b3c69db312dc5edca91fb79dfad4e +size 261 diff --git a/assets/voxygen/element/frames/selection.vox b/assets/voxygen/element/frames/selection.vox deleted file mode 100644 index dece7feb54..0000000000 --- a/assets/voxygen/element/frames/selection.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e800abd210e23b0ba3125929dfbaa191de75ee1f6c54e2d9634c56192023ba2 -size 55156 diff --git a/assets/voxygen/element/frames/selection_frame.png b/assets/voxygen/element/frames/selection_frame.png new file mode 100644 index 0000000000..235d84c356 --- /dev/null +++ b/assets/voxygen/element/frames/selection_frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61c241e368224bbbbf010a188308729de733927dd764d727c08c575216016fec +size 184 diff --git a/assets/voxygen/element/frames/selection_frame.vox b/assets/voxygen/element/frames/selection_frame.vox deleted file mode 100644 index b8c6bc32ae..0000000000 --- a/assets/voxygen/element/frames/selection_frame.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43867e582d418ed695eed2802729b6038e96484473c4b01840256119313c4a36 -size 2288 diff --git a/assets/voxygen/element/frames/selection_hover.png b/assets/voxygen/element/frames/selection_hover.png new file mode 100644 index 0000000000..d72c4bbf53 --- /dev/null +++ b/assets/voxygen/element/frames/selection_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10239624d63369d53b42af95865ee29bec81157a1b085891554d24309bdf544c +size 259 diff --git a/assets/voxygen/element/frames/selection_hover.vox b/assets/voxygen/element/frames/selection_hover.vox deleted file mode 100644 index 7b806be7a9..0000000000 --- a/assets/voxygen/element/frames/selection_hover.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6bd73f31f77e3e4d4f65dbff3b37dda8afefbaf21e226a5f7debdaac8439f2f9 -size 66532 diff --git a/assets/voxygen/element/frames/selection_press.png b/assets/voxygen/element/frames/selection_press.png new file mode 100644 index 0000000000..2aa3260773 --- /dev/null +++ b/assets/voxygen/element/frames/selection_press.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e8d4043fa966b772cfc0222b9996c06407fd3bf756256a4eff3548f2928e2db +size 250 diff --git a/assets/voxygen/element/frames/selection_press.vox b/assets/voxygen/element/frames/selection_press.vox deleted file mode 100644 index e981536ef1..0000000000 --- a/assets/voxygen/element/frames/selection_press.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:beccc5d12589e2467a6803d6e13824a2a5e8e4ff31c0d02e139de7bd0f2a3f99 -size 66532 diff --git a/assets/voxygen/element/frames/tt_test_corner_tr.png b/assets/voxygen/element/frames/tt_test_corner_tr.png new file mode 100644 index 0000000000..a66a135760 --- /dev/null +++ b/assets/voxygen/element/frames/tt_test_corner_tr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0de494ff10082d841254a96d3ea5d87d3bc77e2b7e10662584c86957b810460 +size 134 diff --git a/assets/voxygen/element/frames/tt_test_corner_tr.vox b/assets/voxygen/element/frames/tt_test_corner_tr.vox deleted file mode 100644 index f191188d7c..0000000000 --- a/assets/voxygen/element/frames/tt_test_corner_tr.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d9a0b4cc32a126ec3b31d2074bfaf9a03f6d177632d407df1f115242cfe8e8b -size 56468 diff --git a/assets/voxygen/element/frames/tt_test_edge.png b/assets/voxygen/element/frames/tt_test_edge.png new file mode 100644 index 0000000000..b28d1e380c --- /dev/null +++ b/assets/voxygen/element/frames/tt_test_edge.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a44ab4a366dd977cd2ece967991b1eb98c271b4c07ee763739af3a50478acb4 +size 117 diff --git a/assets/voxygen/element/frames/tt_test_edge.vox b/assets/voxygen/element/frames/tt_test_edge.vox deleted file mode 100644 index 7a090f9e98..0000000000 --- a/assets/voxygen/element/frames/tt_test_edge.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c7ab1c724d9c0b4b7bc80a4cea39e2b483a50a2291b6d497ed1baa0f8595b43b -size 56220 diff --git a/assets/voxygen/element/icons/2hsword_m1.png b/assets/voxygen/element/icons/2hsword_m1.png index 1b36b8a46f..582a57af16 100644 --- a/assets/voxygen/element/icons/2hsword_m1.png +++ b/assets/voxygen/element/icons/2hsword_m1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f442cb6ef061bfee9e66168b9c32e5bdb7e59fde790c0be31f93785857779e6f -size 784 +oid sha256:3f27d1046c6e1a2832b7f43750f27c2aeeb7f44a874e558b5380bcdde5bfe935 +size 466 diff --git a/assets/voxygen/element/icons/2hsword_m2.png b/assets/voxygen/element/icons/2hsword_m2.png index d54cda0e92..bf991d0c42 100644 --- a/assets/voxygen/element/icons/2hsword_m2.png +++ b/assets/voxygen/element/icons/2hsword_m2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b7a2ab9fac21eade39278def1c4e76ce793737c6bd3f4d55037a930ad304e34 -size 929 +oid sha256:9e679ee275cec6cf72962770364d9cf39d164af66ebd03969a0c33fca2dc2ec0 +size 357 diff --git a/assets/voxygen/element/icons/2hsword_slash.png b/assets/voxygen/element/icons/2hsword_slash.png deleted file mode 100644 index 18d36aef24..0000000000 --- a/assets/voxygen/element/icons/2hsword_slash.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b827aa50558455a6ba821e4f032d45ae04d7bfdcecd2597fc1cb6c0b0e6d1973 -size 782 diff --git a/assets/voxygen/element/icons/anvil.png b/assets/voxygen/element/icons/anvil.png new file mode 100644 index 0000000000..d65e6c43d5 --- /dev/null +++ b/assets/voxygen/element/icons/anvil.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7f9c7eecc22a0be5add0a17f7bdfb0967c7d44b605fd3de1ad29ae07a69851a +size 355 diff --git a/assets/voxygen/element/icons/bow_aoe.png b/assets/voxygen/element/icons/bow_aoe.png new file mode 100644 index 0000000000..9fd92299ab --- /dev/null +++ b/assets/voxygen/element/icons/bow_aoe.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05080ad4fdb4ad813e7fc21bac32a18a93f5c0cd049b457844ebedca527ae80b +size 248 diff --git a/assets/voxygen/element/icons/bow_m1.png b/assets/voxygen/element/icons/bow_m1.png index b9cb8af63d..126a0acdd5 100644 --- a/assets/voxygen/element/icons/bow_m1.png +++ b/assets/voxygen/element/icons/bow_m1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff1b52d259aafa8384e05dc5ae2ca3dcd7a88c7a4456149cc7a37e9e0c106ad5 -size 808 +oid sha256:ec6cf7010b6254b782286e087aa84175a78841b89945de90a50f5989e91880d7 +size 317 diff --git a/assets/voxygen/element/icons/bow_m2.png b/assets/voxygen/element/icons/bow_m2.png index 31627cde58..d28141f333 100644 --- a/assets/voxygen/element/icons/bow_m2.png +++ b/assets/voxygen/element/icons/bow_m2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b580f448d4d3d0f86a5f3382842c6ef29507b7717d5c2994e902a0d9d82d8f0b -size 16863 +oid sha256:c689c982806aeefd1608afc5c8bcdf62873e2d0b6ce63d7296e882126c522b84 +size 275 diff --git a/assets/voxygen/element/icons/debug_wand_m1.png b/assets/voxygen/element/icons/debug_wand_m1.png index ae496eec91..87a12f1072 100644 --- a/assets/voxygen/element/icons/debug_wand_m1.png +++ b/assets/voxygen/element/icons/debug_wand_m1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca52786d85797896195c075c1bc91c22f2c87d6a9e64f08dac65d3da3e91e48b -size 839 +oid sha256:d213bb2ce2a0ca2f845eb55934789945e77ff2e82a6ef11224dd8b489245ec0f +size 336 diff --git a/assets/voxygen/element/icons/debug_wand_m2.png b/assets/voxygen/element/icons/debug_wand_m2.png index c0b53ecf35..fb1e60ceb6 100644 --- a/assets/voxygen/element/icons/debug_wand_m2.png +++ b/assets/voxygen/element/icons/debug_wand_m2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50277d1222dd1b25ded80edc443830deb74db368a66dac5c0398112b9e4618cb -size 900 +oid sha256:239e6c33b6ea011bbfb82be7facb9e4aa974e12ac2c6f8d9018301680a36d982 +size 366 diff --git a/assets/voxygen/element/icons/gem.png b/assets/voxygen/element/icons/gem.png new file mode 100644 index 0000000000..29f7af2101 --- /dev/null +++ b/assets/voxygen/element/icons/gem.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52dcdf8dac9caa6c43a10ac35f11439dcc8669f9e34ce8d152ff676f5cf1aaac +size 260 diff --git a/assets/voxygen/element/icons/heal_0.png b/assets/voxygen/element/icons/heal_0.png index bac626be04..e63fee6398 100644 --- a/assets/voxygen/element/icons/heal_0.png +++ b/assets/voxygen/element/icons/heal_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e68b8d6a36b444e17797015e439ed711b865f53a1992762139db2228ca25b686 -size 730 +oid sha256:9231d976f42b42f097a2cf247142ecb837869a33ff4451467e018667fd5d76f3 +size 700 diff --git a/assets/voxygen/element/icons/item_apple.png b/assets/voxygen/element/icons/item_apple.png new file mode 100644 index 0000000000..860aae8b59 --- /dev/null +++ b/assets/voxygen/element/icons/item_apple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5461b53f587f61256bc8ba5472ae397c9a49604bba615a5e70e3d7aef172f450 +size 421 diff --git a/assets/voxygen/element/icons/item_apple.vox b/assets/voxygen/element/icons/item_apple.vox deleted file mode 100644 index b4a60bd3fb..0000000000 --- a/assets/voxygen/element/icons/item_apple.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bec86131cd09411c3b1516d602ecf0db3632d7c7ca5db0408743325e8d2ccfff -size 1748 diff --git a/assets/voxygen/element/icons/item_apple_curry.png b/assets/voxygen/element/icons/item_apple_curry.png new file mode 100644 index 0000000000..5b25b38538 --- /dev/null +++ b/assets/voxygen/element/icons/item_apple_curry.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1dc441e3117b737cb08ab39858c341accbd69916de2c7f539f660a17ed28fee +size 358 diff --git a/assets/voxygen/element/icons/item_apple_stick.png b/assets/voxygen/element/icons/item_apple_stick.png new file mode 100644 index 0000000000..1e0d6b5738 --- /dev/null +++ b/assets/voxygen/element/icons/item_apple_stick.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f0b96e1f3c109908165dad8646d0a6f5d943e8c380ce8f26fd2ed0ff56f7e4a +size 366 diff --git a/assets/voxygen/element/icons/item_cheese.png b/assets/voxygen/element/icons/item_cheese.png index e20c21aa7e..6bd7975c5d 100644 --- a/assets/voxygen/element/icons/item_cheese.png +++ b/assets/voxygen/element/icons/item_cheese.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b73e44bddd679b83b8d2e668589e06316b419777c9ac3818c00fdd01a18affe -size 245 +oid sha256:01c206ada0f3f12a496108699ba8587bc0d8f1549bf448cf74599c27c1b701a1 +size 339 diff --git a/assets/voxygen/element/icons/item_cheese.vox b/assets/voxygen/element/icons/item_cheese.vox deleted file mode 100644 index 5bfc15c670..0000000000 --- a/assets/voxygen/element/icons/item_cheese.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f086a745accda8521bcb21215024b40b111bae815b73937d87a8fd838038e61f -size 1680 diff --git a/assets/voxygen/element/icons/item_coconut.png b/assets/voxygen/element/icons/item_coconut.png index 69f5f9046e..29828b0cb2 100644 --- a/assets/voxygen/element/icons/item_coconut.png +++ b/assets/voxygen/element/icons/item_coconut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0307232e80cbbc31cf5ec9d03483cbd0851334caffe09d3a4d7bcb71a77e5097 -size 344 +oid sha256:ecfe0e0c59fe3f4a0dfd94038e6332434fc096a9b9c251453175c6778f1d0e7b +size 354 diff --git a/assets/voxygen/element/icons/item_leather0.png b/assets/voxygen/element/icons/item_leather0.png new file mode 100644 index 0000000000..f8f60e9180 --- /dev/null +++ b/assets/voxygen/element/icons/item_leather0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec7cc129c46d1daeee085366798f9df2d7d4525d7ccbd21087466d6d1a25a2bc +size 416 diff --git a/assets/voxygen/element/icons/item_mortarpestlecoco.png b/assets/voxygen/element/icons/item_mortarpestlecoco.png new file mode 100644 index 0000000000..f3612606e4 --- /dev/null +++ b/assets/voxygen/element/icons/item_mortarpestlecoco.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3de3c3c6ffc2edea096e10a718022583db28f9a64207adc27f06ce3ec162b1f +size 304 diff --git a/assets/voxygen/element/icons/item_shroom_stick.png b/assets/voxygen/element/icons/item_shroom_stick.png new file mode 100644 index 0000000000..e152d4cb6a --- /dev/null +++ b/assets/voxygen/element/icons/item_shroom_stick.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b213392e6df9d7e45c576aa7a6ce846156beae0777b05852fc87c79eaf307d49 +size 348 diff --git a/assets/voxygen/element/icons/protection.png b/assets/voxygen/element/icons/protection.png new file mode 100644 index 0000000000..06e02352b5 --- /dev/null +++ b/assets/voxygen/element/icons/protection.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6412a1b82a084f9a7828f185752c891d40c5ce8c7575f91edfc360af47cf710f +size 404 diff --git a/assets/voxygen/element/icons/skill_charge_3.png b/assets/voxygen/element/icons/skill_charge_3.png deleted file mode 100644 index 82c5cfae2e..0000000000 --- a/assets/voxygen/element/icons/skill_charge_3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c872af7c35dced7c6b11f2838b6ecc2c0176da2f5e3767c2d76b437a9b2e837 -size 1213 diff --git a/assets/voxygen/element/icons/skill_sword_pierce.png b/assets/voxygen/element/icons/skill_sword_pierce.png new file mode 100644 index 0000000000..6ba4b0b090 --- /dev/null +++ b/assets/voxygen/element/icons/skill_sword_pierce.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d14a405f1e2bdbaaa68dff706fd62437de970323e43c9f4f764a2095b524d8fc +size 389 diff --git a/assets/voxygen/element/icons/snake.png b/assets/voxygen/element/icons/snake.png index 1459f2f094..fef32a8576 100644 --- a/assets/voxygen/element/icons/snake.png +++ b/assets/voxygen/element/icons/snake.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:514d65e989c1ea89f98cb4b45832213c13dc0c36906fd5bc6a8dae2ac9022e00 -size 2722 +oid sha256:bf7a20df6e3d39077dae3ddb3c29da886912c859f909ea63c33bf57201739bdd +size 532 diff --git a/assets/voxygen/element/icons/snake_green.png b/assets/voxygen/element/icons/snake_green.png new file mode 100644 index 0000000000..e05b6172e7 --- /dev/null +++ b/assets/voxygen/element/icons/snake_green.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:669f96ac170211741a50c4f1a2d1f21ee887e7eba4efeeb0ce30675daf2b9fd7 +size 564 diff --git a/assets/voxygen/element/icons/staff_m1.png b/assets/voxygen/element/icons/staff_m1.png index 3955caf46a..717c22eac6 100644 --- a/assets/voxygen/element/icons/staff_m1.png +++ b/assets/voxygen/element/icons/staff_m1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b743bbccb12309114e55a35d21b196f6a97b458d4ea208b48654019c2d24e90 -size 795 +oid sha256:b975bbbcb1573af4c2af4dac991e4b474b15d0eb7e568e24ea68928982e3100a +size 429 diff --git a/assets/voxygen/element/icons/staff_m2.png b/assets/voxygen/element/icons/staff_m2.png index f22841c798..8a62997419 100644 --- a/assets/voxygen/element/icons/staff_m2.png +++ b/assets/voxygen/element/icons/staff_m2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:813f0d30ac437525faa2adb022ce4f57d1beac2ec60d0e9b671c16111eef379c -size 1091 +oid sha256:393fe7cbf2e6626ff83074120b6a89d9ddfdd2435c13c79a493789a41c1403f4 +size 462 diff --git a/assets/voxygen/element/misc_bg/crafting.png b/assets/voxygen/element/misc_bg/crafting.png new file mode 100644 index 0000000000..bd6cc4ac0d --- /dev/null +++ b/assets/voxygen/element/misc_bg/crafting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1800710a64a8bb5be612f4cc6c88acd2c560d9125cf69899b5f35bf9e137aa42 +size 7737 diff --git a/assets/voxygen/element/misc_bg/crafting_frame.png b/assets/voxygen/element/misc_bg/crafting_frame.png new file mode 100644 index 0000000000..602b96bc20 --- /dev/null +++ b/assets/voxygen/element/misc_bg/crafting_frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfa17b2872fc21a8974e3955c14efda8017e87a8de9f72f42b9d5207eeaa1053 +size 15174 diff --git a/assets/voxygen/element/misc_bg/inv_bg.png b/assets/voxygen/element/misc_bg/inv_bg.png index 0c2c9ccf9a..54ad64bfcf 100644 --- a/assets/voxygen/element/misc_bg/inv_bg.png +++ b/assets/voxygen/element/misc_bg/inv_bg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68aa80432b2ec6ff15179a5092b976b8938aa0fa2dba5248536b90ba4838ecd6 -size 58872 +oid sha256:96af9310d82d646b8a1b109e433069ee753d6cf7f5dfd24a2979ecc711fb0048 +size 53053 diff --git a/assets/voxygen/element/misc_bg/map_bg.png b/assets/voxygen/element/misc_bg/map_bg.png index 5afd1cb248..fba5549934 100644 --- a/assets/voxygen/element/misc_bg/map_bg.png +++ b/assets/voxygen/element/misc_bg/map_bg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9e597d8bc0327e5802d68eed05b68953e5c1511ee7698a5091fa31ead38a0e5 -size 5494 +oid sha256:0f1c93ac6021ddd3509fb7b0303cb3e1936a6486bcf3c9de322ee44e0e029339 +size 5070 diff --git a/assets/voxygen/element/misc_bg/window.png b/assets/voxygen/element/misc_bg/window.png new file mode 100644 index 0000000000..a3f9d7fca5 --- /dev/null +++ b/assets/voxygen/element/misc_bg/window.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4aeb912ea4143418b5d90026d094733f442241b6a1e80a9417e5c969778adb3d +size 6415 diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index ef4443695e..1e0c591b3b 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -74,7 +74,7 @@ VoxygenLocalization( "common.connection_lost": r#"Verbindung unterbrochen."#, - "common.species.orc": "Orc", + "common.species.orc": "Ork", "common.species.human": "Mensch", "common.species.dwarf": "Zwerg", "common.species.elf": "Elf", @@ -102,16 +102,16 @@ Bevor es losgeht noch einige Infos: Dies ist eine frühe Alpha. Ihr werdet auf Bugs, unfertiges Gameplay und Mechaniken, sowie fehlende Features stoßen. -Für konstruktives Feedback und Bug-Reports könnt ihr uns via Reddit, Gitlab oder unseren Discord Server kontaktieren. +Für konstruktives Feedback und Bug-Reports könnt Ihr uns via Reddit, Gitlab oder unseren Discord Server kontaktieren. -Veloren hat die GPL 3 Open-Source Lizenz. Das heißt ihr könnt es kostenlos spielen, +Veloren hat die GPL 3 Open-Source Lizenz. Das heißt Ihr könnt es kostenlos spielen, aber auch modifizieren (solange die Mods auch die selbe Lizenz tragen) und das Spiel an andere weiterschicken. Veloren ist ein Non-Profit Community Projekt und jeder Mitarbeiter entwickelt es als Hobby in seiner Freizeit. -Wenn euch die Idee gefällt, dann schließt euch doch einfach unserem Dev- oder Art-Team an! +Wenn Euch die Idee gefällt, dann schließt Euch doch einfach unserem Dev- oder Art-Team an! -Danke, dass ihr euch die Zeit genommen habt diese Zeilen zu lesen und wir hoffen, dass euch Veloren gefällt! +Danke, dass Ihr Euch die Zeit genommen habt diese Zeilen zu lesen und wir hoffen, dass Euch Veloren gefällt! ~ Die Entwickler"#, @@ -120,7 +120,7 @@ Danke, dass ihr euch die Zeit genommen habt diese Zeilen zu lesen und wir hoffen Zum Spielen wird ein Account benötigt. -Diesen könnt ihr euch hier erstellen: +Diesen könnt Ihr Euch hier erstellen: https://account.veloren.net. @@ -161,7 +161,7 @@ eurer erstellen Charaktere gespeichert."#, "hud.chat.loot_msg": "Ihr erhaltet [{item}]", "hud.chat.loot_fail": "Euer Inventar ist voll!", "hud.chat.goodbye": "Verbindung getrennt.", - "hud.chat.connection_lost": "Verbindung unterbrochen. Trenne Verbindung in {time} sekunden.", + "hud.chat.connection_lost": "Verbindung unterbrochen. Trenne Verbindung in {time} Sekunden.", // SCT outputs "hud.sct.experience": "{amount} Erf", @@ -174,7 +174,7 @@ eurer erstellen Charaktere gespeichert."#, "hud.welcome": r#"Willkommen zur Veloren Alpha. -Einige Tipps bevor ihr startet: +Einige Tipps bevor Ihr beginnt: Drückt F1, um die Tastenbelegungen zu sehen. @@ -186,9 +186,9 @@ Um Chat-Kommandos zu sehen gebt /help in den Chat ein. Sammelt diese mit Rechts-Klick auf. -Um diese zu nutzen öffnet euer Inventar mit 'B'. +Um diese zu nutzen öffnet Euer Inventar mit 'B'. -Doppelklickt den Gegenstand in eurer Tasche, um diesen zu nutzen. +Doppelklickt den Gegenstand in Eurer Tasche, um diesen zu nutzen. Um Items wegzuwerfen klickt sie einmal im Inventar an @@ -197,10 +197,10 @@ und klickt dann außerhalb der Tasche. Die Nächte in Veloren können sehr dunkel werden. -Drückt 'G' um eure Laterne einzuschalten. +Drückt 'G' um Eure Laterne einzuschalten. -Ihr wollt endlich spielen und dafür euren Cursor befreien, +Ihr wollt endlich spielen und dafür Euren Cursor befreien, um dieses Fenster zu schließen? Drückt 'TAB'! @@ -216,7 +216,7 @@ Versammelt einige Kämpfer, sucht etwas Nahrung und besiegt ihre abscheulichen Anführer und Akolyten. -Vielleicht könnt ihr sogar einen ihrer +Vielleicht könnt Ihr sogar einen ihrer magischen Gegenstände ergattern?"#, // Inventory @@ -313,6 +313,8 @@ magischen Gegenstände ergattern?"#, "hud.settings.audio_device": "Ausgabegerät", "hud.settings.awaitingkey": "Drückt eine Taste...", + "hud.settings.unbound": "-", + "hud.settings.reset_keybinds": "Auf Standard zurücksetzen", "hud.social": "Sozial", "hud.social.online": "Online", @@ -323,6 +325,12 @@ magischen Gegenstände ergattern?"#, "hud.spell": "Zauber", + "hud.crafting": "Herstellen", + "hud.crafting.recipes": "Rezepte", + "hud.crafting.ingredients": "Zutaten:", + "hud.crafting.craft": "Herstellen", + "hud.crafting.tool_cata": "Benötigt:", + "hud.free_look_indicator": "Freie Sicht aktiv", "hud.auto_walk_indicator": "Automatisches Laufen aktiv", @@ -409,8 +417,7 @@ magischen Gegenstände ergattern?"#, /// End chracter selection section /// Start character window section "character_window.character_name": "Charakter", - // Charater stats - // Charater stats + // Character stats "character_window.character_stats": r#"Ausdauer Beweglichkeit @@ -429,8 +436,26 @@ Willenskraft }, vector_map: { + "loading.tips": [ + "Drückt 'G', um Eure Laterne anzuzünden.", + "Drückt 'F1', um die Standard Tastenbelegung anzuzeigen.", + "Ihr könnt /say oder /s verwenden, um den Spielern in Eurer Nähe zu schreiben.", + "Verwendet /region oder /r, um mit Spielern in Eurem Gebiet zu schreiben.", + "Mit /tell könnt ihr einem Mitspieler direkte Nachrichten schicken.", + "NPCs mit demselben Level können unterschiedlich schwierig zu besiegen sein.", + "Behaltet den Boden um euch im Blick! Dort gibt es Nahrung, Kisten und Anderes zu finden.", + "Ist Euer Inventar voll mit Nahrung? Wertet es einfach durch Crafting auf!", + "Ihr sucht nach einem Abenteuer? Dungeons sind mit braunen Markierungen auf der Karte vermerkt!", + "Vergesst nicht Eure Grafikeinstellungen anzupassen! Mit 'N' kommt ihr in die Einstellungen.", + "Zusammen kämpfen macht mehr Spaß! Drückt 'O' um Eure Mitspieler anzuzeigen.", + "Ein NPC mit einem Schädel unter seiner Lebensanzeige ist deutlich stärker als Ihr.", + "Drückt 'J' um zu tanzen. Yeah!", + "Verwendet 'L-Shift' um mit Eurem Gleiter den Himmel zu erorbern!", + "Veloren befindet sich noch in der Pre-Alpha Phase. Wir tun unser Bestes, um das Spielgefühl jeden Tag zu verbessern!", + "Ihr wünscht, Euch mit uns auszutauschen oder wollt unserem Entwickler-Team beitreten? Kommt doch einfach auf unseren Discord-Server!", + ], "npc.speech.villager_under_attack": [ "Hilfe, ich werde angegriffen!", - ], + ], } ) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 38102fc91d..aba20a4d32 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -95,6 +95,7 @@ Is the client up to date?"#, /// Start Main screen section "main.connecting": "Connecting", "main.creating_world": "Creating world", + "main.tip": "Tip:", // Welcome notice that appears the first time Veloren is started "main.notice": r#"Welcome to the alpha version of Veloren! @@ -118,8 +119,6 @@ Thanks for taking the time to read this notice, we hope you enjoy the game! // Login process description "main.login_process": r#"Information on the Login Process: -If you are having issues signing in: - Please note that you now need an account to play on auth-enabled servers. @@ -273,6 +272,7 @@ magically infused items?"#, "hud.settings.chat": "Chat", "hud.settings.background_transparency": "Background Transparency", "hud.settings.chat_character_name": "Character Names in chat", + "hud.settings.loading_tips": "Loading Screen Tips", "hud.settings.pan_sensitivity": "Pan Sensitivity", "hud.settings.zoom_sensitivity": "Zoom Sensitivity", @@ -315,6 +315,8 @@ magically infused items?"#, "hud.settings.audio_device": "Audio Device", "hud.settings.awaitingkey": "Press a key...", + "hud.settings.unbound": "None", + "hud.settings.reset_keybinds": "Reset to Defaults", "hud.social": "Social", "hud.social.online": "Online", @@ -323,7 +325,13 @@ magically infused items?"#, "hud.social.faction": "Faction", "hud.social.play_online_fmt": "{nb_player} player(s) online", - "hud.spell": "Spells", + "hud.crafting": "Crafting", + "hud.crafting.recipes": "Recipes", + "hud.crafting.ingredients": "Ingredients:", + "hud.crafting.craft": "Craft", + "hud.crafting.tool_cata": "Requires:", + + "hud.spell": "Spells", "hud.free_look_indicator": "Free look active", "hud.auto_walk_indicator": "Auto walk active", @@ -414,12 +422,14 @@ magically infused items?"#, /// Start character window section "character_window.character_name": "Character Name", - // Charater stats + // Character stats "character_window.character_stats": r#"Endurance Fitness Willpower + +Protection "#, /// End character window section @@ -427,10 +437,30 @@ Willpower /// Start Escape Menu Section "esc_menu.logout": "Logout", "esc_menu.quit_game": "Quit Game", - /// End Escape Menu Section + /// End Escape Menu Section + }, - vector_map: { + + vector_map: { + "loading.tips": [ + "Press 'G' to light your lantern.", + "Press 'F1' to see all default keybindings.", + "You can type /say or /s to only chat with players directly around you.", + "You can type /region or /r to only chat with players a couple of hundred blocks around you.", + "To send private message type /tell followed by a player name and your message.", + "NPCs with the same level can have a different difficulty.", + "Look at the ground for food, chests and other loot!", + "Inventory filled with food? Try crafting better food from it!", + "Wondering what's there to do? Dungeons are marked with brown spots on the map!", + "Don't forget to adjust the graphics for your system. Press 'N' to open the settings.", + "Playing with others is fun! Press 'O' to see who is online.", + "An NPC with a skull beneath their healthbar is quite powerful compared to yourself.", + "Press 'J' to dance. Party!", + "Press 'L-Shift' to open your Glider and conquer the skies.", + "Veloren is still in Pre-Alpha. We do our best to improve it every day!", + "If you want to join the Dev-Team or just have a chat with us join our Discord-Server.", + ], "npc.speech.villager_under_attack": [ "Help, I'm under attack!", "Help! I'm under attack!", @@ -511,6 +541,6 @@ Willpower "Please don't do that again.", "Guards, throw this monster in the lake!", "I'll set my tarrasque on you!", - ], + ], } ) diff --git a/assets/voxygen/i18n/es_la.ron b/assets/voxygen/i18n/es_la.ron index 3f8eb56bc5..092de3d000 100644 --- a/assets/voxygen/i18n/es_la.ron +++ b/assets/voxygen/i18n/es_la.ron @@ -155,6 +155,18 @@ https://account.veloren.net."#, "hud.press_key_to_toggle_keybindings_fmt": "Presiona {key} para alternar los controles del teclado", "hud.press_key_to_toggle_debug_info_fmt": "Presiona {key} para alternar la información de depuración", + // Chat outputs + "hud.chat.online_msg": "[{name}] se ha conectado.", + "hud.chat.offline_msg": "{name} se ha desconectado.", + "hud.chat.loot_msg": "Recogiste [{item}]", + "hud.chat.loot_fail": "Tu inventario está lleno!", + "hud.chat.goodbye": "Adiós!", + "hud.chat.connection_lost": "Conexión perdida. Expulsando en {time} segundos.", + + // SCT outputs + "hud.sct.experience": "{amount} Exp", + "hud.sct.block": "BLOQUEADO", + /// Respawn message "hud.press_key_to_respawn": r#"Presiona {key} para reaparecer en la ultima fogata que visitaste."#, @@ -252,12 +264,15 @@ objetos infundidos con magia?"#, "hud.settings.cumulated_damage": "Daño Acumulado", "hud.settings.incoming_damage": "Daño Recibido", "hud.settings.cumulated_incoming_damage": "Daño Recibido Acumulado", - "hud.settings.speech_bubble_dark_mode": "Burbujas de Dialogo en Modo Oscuro", + "hud.settings.speech_bubble": "Burbuja de Diálogo", + "hud.settings.speech_bubble_dark_mode": "Burbuja de Diálogo en Modo Oscuro", + "hud.settings.speech_bubble_icon": "Icono de Burbuja de Diálogo", "hud.settings.energybar_numbers": "Números de la Barras de Energia", "hud.settings.values": "Valores", "hud.settings.percentages": "Porcentajes", "hud.settings.chat": "Chat", "hud.settings.background_transparency": "Transparencia de Fondo", + "hud.settings.chat_character_name": "Nombres de Personajes en el chat", "hud.settings.pan_sensitivity": "Sensibilidad de Desplazamiento", "hud.settings.zoom_sensitivity": "Sensibilidad del Zoom", @@ -288,6 +303,8 @@ objetos infundidos con magia?"#, "hud.settings.audio_device": "Dispositivo de Audio", "hud.settings.awaitingkey": "Presiona una tecla...", + "hud.settings.unbound": "Ninguno", + "hud.settings.reset_keybinds": "Reestablecer a los valores predeterminados", "hud.social": "Social", "hud.social.online": "En Linea", @@ -299,7 +316,7 @@ objetos infundidos con magia?"#, "hud.spell": "Hechizos", "hud.free_look_indicator": "Vista libre activa", - "hud.auto_walk_indicator": "Auto caminar activada", + "hud.auto_walk_indicator": "Auto caminar activado", /// End HUD section diff --git a/assets/voxygen/i18n/fr_FR.ron b/assets/voxygen/i18n/fr_FR.ron index 42d996bb0d..478b6cf011 100644 --- a/assets/voxygen/i18n/fr_FR.ron +++ b/assets/voxygen/i18n/fr_FR.ron @@ -49,8 +49,12 @@ VoxygenLocalization( "common.yes": "Oui", "common.no": "Non", "common.okay": "Compris", + "common.accept": "Accepter", + "common.disclaimer": "Avertissement", "common.cancel": "Annuler", "common.none": "Aucun", + "common.error": "Erreur", + "common.fatal_error": "Erreur Fatale", "common.species.orc": "Orc", "common.species.human": "Humain", @@ -89,34 +93,59 @@ Merci d'avoir pris le temps de lire cette notice, nous esperons que vous appreci ~ L'équipe de Veloren"#, - + //Informations de connection "main.login_process": r#"Information sur la procédure de connexion: -L'identifiant de connexion sera votre pseudo en jeu. +Vous devez à présent posséder un compte +afin de jouer sur les serveurs avec authentification. -Le nom et l'apparence que vous choisirez seront -sauvegardés sur votre ordinateur. +Vous pouvez créer un compte à l'adresse -Les niveaux/objets ne sont pas sauvegardés."#, +https://account.veloren.net."#, + "main.login.server_not_found": "Serveur introuvable", + "main.login.authentication_error": "Erreur d'authentification sur le serveur", + "main.login.server_full": "Serveur plein", + "main.login.untrusted_auth_server": "Le serveur d'authentification n'est pas de confiance", + "main.login.outdated_client_or_server": "ServeurPasContent: Les versions sont probablement incompatibles, verifiez les mises à jour.", + "main.login.timeout": "DélaiEcoulé: Le serveur n'a pas repondu à temps. (Surchage ou Problèmes réseau).", + "main.login.server_shut_down": "Extinction du Serveur", + "main.login.already_logged_in": "Vous êtes déjà connecté à ce serveur.", + "main.login.network_error": "Problème Réseau", + "main.login.failed_sending_request": "Demande d'authentification serveur échouée", + "main.login.invalid_character": "Le personnage sélectionné n'est pas valide", + "main.login.client_crashed": "Le client a planté", + "main.login.not_on_whitelist": "Vous devez être ajouté à la liste blanche par un Admin pour pouvoir entrer", + "main.tip": "Astuce:", + /// End Main screen section - // HUD texts - "hud.you_died": "Vous êtes mort", + ///Début section Hud "hud.do_not_show_on_startup": "Ne pas afficher au démarage", - "hud.press_key_to_show_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles", - "hud.settings.toggle_shortcuts": "Activer les raccourcis", "hud.show_tips": "Voir les astuces", "hud.quests": "Quêtes", - "hud.spell": "Sorts", - "hud.press_key_to_toggle_debug_info_fmt": "Appuyer sur {key} pour activer les informations de debogage", + "hud.you_died": "Vous êtes mort", + "hud.waypoint_saved": "Point de Repère Sauvegardé", + + + "hud.press_key_to_show_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles", "hud.press_key_to_show_debug_info_fmt": "Appuyer sur {key} pour afficher les informations de debogage", + "hud.press_key_to_toggle_debug_info_fmt": "Appuyer sur {key} pour activer les informations de debogage", "hud.press_key_to_toggle_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles", + // Sorties Tchat + "hud.chat.online_msg": "[{name}] est maintenant en ligne.", + "hud.chat.offline_msg": "{name} s'est déconnecté.", + "hud.chat.loot_msg": "Vous avez ramassé [{item}]", + "hud.chat.loot_fail": "Votre inventaire est plein!", + "hud.chat.goodbye": "Au revoir!", + "hud.chat.connection_lost": "Connection perdue. Expulsion dans {time} secondes.", - /// Respawn message - "hud.press_key_to_respawn": r#"Appuyer sur {key} pour revivre à votre point de repère. + // Sorties SCT + "hud.sct.experience": "{amount} Exp", + "hud.sct.block": "BLOQUÉ", -Appuyez sur Entrée, taper /waypoint pour configurer le point de repère à l'endroit actuel"#, + // Respawn message + "hud.press_key_to_respawn": r#"Appuyez sur {key} pour réapparaitre au dernier feu de camp visité"#, /// Welcome message @@ -153,54 +182,93 @@ Vous souhaitez libérer votre souris pour fermer cette fenêtre? Tapez sur TAB! Profitez de votre séjour dans le monde de Veloren."#, -"hud.temp_quest_headline": r#"Please, help us Traveller!"#, -"hud.temp_quest_text": r#"Dungeons filled with evil cultists -have emerged all around our peaceful towns! +"hud.temp_quest_headline": r#"S'il vous plaît, aidez nous voyageur!"#, +"hud.temp_quest_text": r#"Des donjons remplis de cultistes malfaisants +sont apparus tout autour de nos paisibles villages! -Gather some company, stack up on food -and defeat their vile leaders and acolytes. +Trouvez des alliés, faites des provisions +et venez à bout de leurs chefs ainsi que de leurs sbires. -Maybe you can even obtain one of their -magically infused items?"#, +Peut être pourrez-vous même obtenir un de leurs +objets magiques ?"#, + + + // Inventaire + "hud.bag.inventory": "Inventaire de {playername}", + "hud.bag.stats_title": "Attributs de {playername}", + "hud.bag.exp": "Exp", + "hud.bag.armor": "Armure", + "hud.bag.stats": "Attributs", + "hud.bag.head": "Tête", + "hud.bag.neck": "Cou", + "hud.bag.tabard": "Tabar", + "hud.bag.shoulders": "Epaules", + "hud.bag.chest": "Torse", + "hud.bag.hands": "Mains", + "hud.bag.lantern": "Lanterne", + "hud.bag.belt": "Ceinture", + "hud.bag.ring": "Bague", + "hud.bag.back": "Dos", + "hud.bag.legs": "Jambes", + "hud.bag.feet": "Pieds", + "hud.bag.mainhand": "Main Dominante", + "hud.bag.offhand": "Main Dominée", + + // Carte et journal de quetes + "hud.map.map_title": "Carte", + "hud.map.qlog_title": "Quêtes", + + + //Paramètres "hud.settings.general": "Général", + "hud.settings.none": "Aucun", + "hud.settings.press_behavior.toggle": "Activer/Desactiver", + "hud.settings.press_behavior.hold": "Maintenir", "hud.settings.help_window": "Fenêtre d'aide", "hud.settings.debug_info": "Information de débogage", "hud.settings.tips_on_startup": "Astuces au démarrage", - "hud.settings.ui_scale": "Echelle de l'interface", "hud.settings.relative_scaling": "Echelle relative", "hud.settings.custom_scaling": "Echelle personalisée", - "hud.settings.crosshair": "Réticule", "hud.settings.transparency": "Transparence", - "hud.settings.hotbar": "Barre active", + "hud.settings.hotbar": "Barre d'action", + "hud.settings.toggle_shortcuts": "Activer les raccourcis", "hud.settings.toggle_bar_experience": "Activer la barre d'experience", - "hud.settings.scrolling_combat_text": "Dégats de combat", "hud.settings.single_damage_number": "Dégat adversaire (par dégat)", "hud.settings.cumulated_damage": "Dégat adversaire (cumulé)", "hud.settings.incoming_damage": "Dégat personnage (par dégat)", "hud.settings.cumulated_incoming_damage": "Dégat personnage (cumulé)", - + "hud.settings.speech_bubble": "Bulle de dialogue", + "hud.settings.speech_bubble_dark_mode": "Bulle de dialogue Mode Sombre", + "hud.settings.speech_bubble_icon": "Icône Bulle de dialogue", "hud.settings.energybar_numbers": "Nombre des barres d'energie", - "hud.settings.none": "Aucuns", "hud.settings.values": "Valeurs", "hud.settings.percentages": "Pourcentages", - "hud.settings.chat": "Tchat", "hud.settings.background_transparency": "Transparence du fond", + "hud.settings.chat_character_name": "Nom des personnages dans le tchat", + "hud.settings.loading_tips": "Astuces de chargement", "hud.settings.pan_sensitivity": "Sensibilité de la souris", "hud.settings.zoom_sensitivity": "Sensibilité du zoom", "hud.settings.invert_scroll_zoom": "Inverser la molette", "hud.settings.invert_mouse_y_axis": "Inverser l'axe Y", + "hud.settings.enable_mouse_smoothing": "Lissage Camera", + "hud.settings.free_look_behavior": "Comportement Vue libre", + "hud.settings.auto_walk_behavior": "Comportement Marche Automatique", + "hud.settings.stop_auto_walk_on_input": "Arrêt marche auto si mouvement", "hud.settings.view_distance": "Distance d'affichage", + "hud.settings.sprites_view_distance": "Distance d'affichage des sprites", + "hud.settings.figures_view_distance": "Distance d'affichage des entités", "hud.settings.maximum_fps": "Limite FPS", "hud.settings.fov": "Champs de vision (degrés)", + "hud.settings.gamma": "Gamma", "hud.settings.antialiasing_mode": "Mode anticrénelage", "hud.settings.cloud_rendering_mode": "Rendu des nuages", "hud.settings.fluid_rendering_mode": "Rendu des fluides", @@ -214,87 +282,9 @@ magically infused items?"#, "hud.settings.sound_effect_volume": "Volume des effets", "hud.settings.audio_device": "Périphérique audio", - "hud.settings.control_names": r#"Libérer le curseur -Activer la fenêtre d'aide -Activer l'interface -Activer la fenêtre de débogage -Prendre une capture d'écran -Activer les noms de personnage -Activer le mode plein écran - - -Avancer -Aller à gauche -Aller à droite -Reculer - -Sauter - -Planer - -Esquiver - -Roulade - -Escalader - -Desescalader - -Marche automatique - -Ranger/Sortir les armes - -Mettre/Enlever le casque - -S'assoir - -Monter - -Intéragir - - -Attaque basique -Attaque secondaire/Bloquer/Viser - - -Barre de compétence 1 -Barre de compétence 2 -Barre de compétence 3 -Barre de compétence 4 -Barre de compétence 5 -Barre de compétence 6 -Barre de compétence 7 -Barre de compétence 8 -Barre de compétence 9 -Barre de compétence 10 - - -Pause -Paramètres -Social -Carte -Livre de sorts -Personnages -Livre de quêtes -Sac à dos - - - -Envoyer un message -Défiler dans le tchat - - -Commandes du tchat: - -/alias [Nom] - Changer de nom -/tp [Nom] - Se téléporter vers un autre joueur -/jump - Sauter à partir de votre position -/goto - Se téléporter à une position -/kill - Se suicider -/pig - Faire aparaitre un cochon PNJ -/wolf - Faire aparaitre un loup PNJ -/help - Afficher les commandes de tchat"#, - + "hud.settings.awaitingkey": "Appuyez sur une touche...", + "hud.settings.unbound": "Aucun", + "hud.settings.reset_keybinds": "Réinitialiser touches par défaut", "hud.social": "Social", "hud.social.friends": "Amis", @@ -302,30 +292,102 @@ Commandes du tchat: "hud.social.online": "Jeu en ligne", "hud.social.not_yet_available": "Pas encore disponible", "hud.social.play_online_fmt": "{nb_player} joueurs en ligne", - "char_selection.change_server": "Changer de serveur", - "char_selection.delete_permanently": "Supprimer", + "hud.crafting": "Fabrication", + "hud.crafting.recipes": "Recettes", + "hud.crafting.ingredients": "Ingrédients:", + "hud.crafting.craft": "Fabriquer", + "hud.crafting.tool_cata": "Nécessite:", + "hud.spell": "Sorts", + + "hud.free_look_indicator": "Vue libre active", + "hud.auto_walk_indicator": "Marche automatique active", + + //Fin Section Hud + + /// Debut de section GameInput + "gameinput.primary": "Attaque Basique", + "gameinput.secondary": "Attaque Secondaire/Bloquer/Viser", + "gameinput.slot1": "Emplacement rapide 1", + "gameinput.slot2": "Emplacement rapide 2", + "gameinput.slot3": "Emplacement rapide 3", + "gameinput.slot4": "Emplacement rapide 4", + "gameinput.slot5": "Emplacement rapide 5", + "gameinput.slot6": "Emplacement rapide 6", + "gameinput.slot7": "Emplacement rapide 7", + "gameinput.slot8": "Emplacement rapide 8", + "gameinput.slot9": "Emplacement rapide 9", + "gameinput.slot10": "Emplacement rapide 10", + "gameinput.swaploadout": "Échanger l'équipement", + "gameinput.togglecursor": "Activer/Desactiver Curseur", + "gameinput.help": "Activer/Desactiver Fenêtre d'aide", + "gameinput.toggleinterface": "Activer/Desactiver Interface", + "gameinput.toggledebug": "Activer/Desactiver IPS et Infos Debogage", + "gameinput.screenshot": "Prendre une capture d'écran", + "gameinput.toggleingameui": "Activer/Desactiver Noms de joueurs", + "gameinput.fullscreen": "Activer/Desactiver Plein Ecran", + "gameinput.moveforward": "Avancer", + "gameinput.moveleft": "Aller à Gauche", + "gameinput.moveright": "Aller à Droite", + "gameinput.moveback": "Reculer", + "gameinput.jump": "Sauter", + "gameinput.glide": "Planeur", + "gameinput.roll": "Rouler", + "gameinput.climb": "Grimper", + "gameinput.climbdown": "Descendre", + "gameinput.wallleap": "Saut Mural", + "gameinput.togglelantern": "Activer/Desactiver Lanterne", + "gameinput.mount": "Monture", + "gameinput.enter": "Entrer", + "gameinput.command": "Commande", + "gameinput.escape": "Fuir", + "gameinput.map": "Carte", + "gameinput.bag": "Sac", + "gameinput.social": "Social", + "gameinput.sit": "S'asseoir", + "gameinput.spellbook": "Sorts", + "gameinput.settings": "Paramètres", + "gameinput.respawn": "Réapparaître", + "gameinput.charge": "Charger", + "gameinput.togglewield": "Ranger Arme", + "gameinput.interact": "Interagir", + "gameinput.freelook": "Vue Libre", + "gameinput.autowalk": "Marche Automatique", + "gameinput.dance": "Dancer", + + /// End GameInput section + + /// Debut Section Menu Start Quitter "esc_menu.quit_game": "Quitter le jeu", "esc_menu.logout": "Se déconnecter", + /// Fin Section Menu Start Quitter + /// Debut de la section Création du personnage + "char_selection.accessories": "Accessoires", "char_selection.beard": "Barbe", - "char_selection.hair_style": "Coupe de cheveux", - "char_selection.hair_color": "Couleur des cheveux", - "char_selection.skin": "Couleur de peau", + "char_selection.create_new_charater": "Créer un nouveau personnage", + "char_selection.creating_character": "Création du personnage...", + "char_selection.character_creation": "Création de personnages", + "char_selection.create_info_name": "Votre personnage doit avoir un prénom !", + "char_selection.deleting_character": "Suppression du personnage...", + "char_selection.enter_world": "Entrer dans le monde", "char_selection.eyebrows": "Sourcils", "char_selection.eye_color": "Couleur des yeux", - "char_selection.accessories": "Accessoires", - "char_selection.level_fmt": "Niveau {level_nb}", - "char_selection.create_new_charater": "Créer un nouveau personnage", + "char_selection.eyeshape": "Forme des yeux", + "char_selection.hair_style": "Coupe de cheveux", + "char_selection.hair_color": "Couleur des cheveux", "char_selection.human_default": "Humain par défault", - "char_selection.uncanny_valley": "Vallée dérangeante", - "char_selection.chest_color": "Couleur du torse", + "char_selection.level_fmt": "Niveau {level_nb}", "char_selection.logout": "Se déconnecter", - "char_selection.enter_world": "Entrer dans le monde", + "char_selection.loading_characters": "Chargement des personnages...", "char_selection.plains_of_uncertainty": "Plaines de l'incertitude", - "char_selection.character_creation": "Création de personnages", + "char_selection.skin": "Couleur de peau", + "char_selection.uncanny_valley": "Vallée dérangeante", + "char_selection.change_server": "Changer de serveur", + "char_selection.delete_permanently": "Supprimer définitivement ce personnage ?", + /// Fin de la section Création du personnage "character_window.character_name": "Personnage", "character_window.character_stats": r#"Endurance @@ -334,9 +396,117 @@ Force Dexterité -Intelligence"#, +Protection +"#, + /// End character window section + + + /// Start Escape Menu Section + "esc_menu.logout": "Déconnexion", + "esc_menu.quit_game": "Quitter le jeu", + /// End Escape Menu Section + }, - vector_map: { + vector_map: { + "loading.tips": [ + "Appuie sur 'G' pour allumer ta lanterne.", + "Appuie sur 'F1' pour voir les raccourcis clavier par défaut.", + "Tu peux écrire /say ou /s pour discuter aux joueurs directement à côté toi.", + "Tu peux écrire /region ou /r pour discuter avec les joueurs situés à quelques centaines de blocs de toi.", + "Pour envoyer un message privé, écrit /tell suivi par un nom de joueur puis ton message.", + "Des PNJs avec le même niveau peuvent varier en difficulté.", + "Regarde le sol pour trouver de la nourriture, des coffres et d'autres butins!", + "Ton inventaire est rempli de nourriture? Essaie de créer un meilleur repas avec!", + "Tu cherches une activité? Les donjons sont marqués avec des points marron sur la carte!", + "N'oublie pas d'ajuster les graphiques pour ton système. Appuie sur 'N' pour ouvrir les paramètres.", + "Jouer à plusieurs est amusant! Appuie sur 'O' pour voir qui est en ligne.", + "Un PNJ avec une tête-de-mort sous sa barre de vie est plus puissant que toi.", + "Appuie sur 'J' pour danser. C'est la fête!", + "Appuie sur 'L-Shift'pour ouvrir ton deltaplane et conquérir les cieux.", + "Veloren est encore Pre-Alpha. Nous faisons de notre mieux pour l'améliorer chaque jour!", + "Si tu veux te joindre à l'équipe de développement ou juste discuter avec nous, rejoins notre serveur Discord.", + ], + "npc.speech.villager_under_attack": [ + "À l'aide, on m'attaque!", + "À l'aide! On m'attaque!", + "Aïe! On m'attaque!", + "Aïe! On m'attaque! À l'aide!", + "Aidez-moi! On m'attaque!", + "On m'attaque! À l'aide!", + "On m'attaque! Aidez-moi!", + "À l'aide!", + "À l'aide! À l'aide!", + "À l'aide! À l'aide! À l'aide!", + "On m'attaque!", + "AAAHHH! On m'attaque!", + "AAAHHH! On m'attaque! À l'aide!", + "À l'aide! Nous sommes attaqués!", + "À l'aide! Assassin!", + "À l'aide! Il y a un assassin en liberté!", + "À l'aide! On essaie de me tuer!", + "Gardes, on m'attaque!", + "Gardes! On m'attaque!", + "On m'attaque! Gardes!", + "À l'aide! Gardes! On m'attaque!", + "Gardes! Venez vite!", + "Gardes! Gardes!", + "Gardes! Un scélérat m'attaque!", + "Gardes, abattez ce scélérat!", + "Gardes! Il y a un meurtrier!", + "Gardes! Aidez-moi!", + "Vous ne vous en tirerez pas comme ça! Gardes!", + "Monstre!", + "Aidez-moi!", + "À l'aide! S'il vous plait!", + "Aïe! Gardes! À l'aide!", + "Ils viennent pour moi !", + "À l'aide! À l'aide! Je me fais réprimer!", + "Ah, nous voyons maintenant la violence inhérente au système.", + "C'est seulement une égratignure.", + "Arrêtez ça!", + "Qu'est ce que je t'ai fait?!", + "S'il te plaît arrête de m'attaquer!", + "Hé! Regardez où vous pointez cette chose!", + "Misérable, allez-vous-en!", + "Arrêtez! Partez! Arrêtez!", + "Vous m'avez ennervé!", + "Oi! Qui croyez-vous être?!", + "J'aurais votre tête pour ça!", + "Stoppez s'il vous plaît! Je ne transporte rien de valeur!", + "Je vais appeler mon frère, il est plus grand que moi!", + "Nooon, Je vais le dire à ma mère!", + "Soyez maudit!", + "Ne faites pas ça.", + "Ce n'était pas très gentil!", + "Ton arme fonctionne, tu peux la ranger maintenant!", + "Épargnez-moi!", + "Pitié, J'ai une famille!", + "Je suis trop jeune pour mourrir!", + "On peut en parler?", + "La violence n'est jamais la solution!", + "Aujourd'hui est une très mauvaise journée...", + "Hé, ça fait mal!", + "Aïe!", + "Quelle impolitesse!", + "Stop, je vous en prie!", + "Que la peste vous emporte!", + "Ce n'est pas amusant.", + "Comment osez-vous?!", + "Vous allez payer!", + "Continue et tu vas le regretter!", + "Ne m'obligez pas à vous faire du mal!", + "Il doit y avoir erreur!", + "Vous n'avez pas besoin de faire ça!", + "Fuyez, monstre!", + "Ça fait vraiment mal!", + "Pourquoi faites-vous cela?", + "Par les esprits, cessez!", + "Vous devez m'avoir confondu avec quelqu'un d'autre!", + "Je ne mérite pas cela!", + "Ne faites plus cela.", + "Gardes, jetez ce monstre dans le lac!", + "Je vais t'envoyer ma tarrasque!", + ], } -) +) \ No newline at end of file diff --git a/assets/voxygen/i18n/it_IT.ron b/assets/voxygen/i18n/it_IT.ron index 657821da47..bcefb59a0e 100644 --- a/assets/voxygen/i18n/it_IT.ron +++ b/assets/voxygen/i18n/it_IT.ron @@ -65,6 +65,7 @@ VoxygenLocalization( "common.back": "Indietro", "common.create": "Crea", "common.okay": "Ok", + "common.accept": "Accetta", "common.disclaimer": "Disclaimer", "common.cancel": "Cancella", "common.none": "Nessuno", @@ -139,11 +140,6 @@ Se ti piace ciò che vedi, sei il benvenuto ad unirti ai team di sviluppo e arti -Come loro, stiamo cercando di costruire una nicchia. Il gioco non è un clone, e il suo sviluppo divergerà dai giochi esistenti in futuro. - - - - Grazie per aver dedicato del tempo a leggere questo avviso, speriamo che ti divertirai col gioco! @@ -176,7 +172,9 @@ https://account.veloren.net."#, "main.login.already_logged_in": "Hai già effettuato l'accesso al server", "main.login.network_error": "Errore di rete", "main.login.failed_sending_request": "Richiesta ai server di autenticazione fallita", + "main.login.invalid_character": "Il personaggio selezionato è invalido", "main.login.client_crashed": "Il client si è arrestato", + "main.login.not_on_whitelist": "Hai bisogno di un permesso di accesso da un admin per entrare", @@ -200,22 +198,31 @@ https://account.veloren.net."#, "hud.show_tips": "Mostra consigli", "hud.quests": "Missioni", "hud.you_died": "Sei Morto", + "hud.waypoint_saved": "Waypoint Salvato", "hud.press_key_to_show_keybindings_fmt": "Premi {key} per mostrare le scorciatoie da tastiera", "hud.press_key_to_show_debug_info_fmt": "Premi {key} per mostrare le informazioni di debug", "hud.press_key_to_toggle_keybindings_fmt": "Premi {key} per attivare/disattivare le scorciatoie da tastiera", "hud.press_key_to_toggle_debug_info_fmt": "Premi {key} per attivare/disattivare le informazioni di debug", + // Chat outputs + "hud.chat.online_msg": "[{name}] è ora online.", + "hud.chat.offline_msg": "{name} è andato offline.", + "hud.chat.loot_msg": "Hai raccolto [{item}]", + "hud.chat.loot_fail": "Il tuo inventario è pieno!", + "hud.chat.goodbye": "Addio!", + "hud.chat.connection_lost": "Connessione persa. Espulsione in {time} secondi.", + + // SCT outputs + "hud.sct.experience": "{amount} Esp", + "hud.sct.block": "PARATO", + + // Respawn message - "hud.press_key_to_respawn": r#"Premi {key} per rinascere al tuo Waypoint. - - - - -Premi Invio, scrivi /waypoint e conferma per impostarlo qui."#, + "hud.press_key_to_respawn": r#"Premi {key} per rinascere all'ultimo falò visitato."#, @@ -229,16 +236,6 @@ Premi Invio, scrivi /waypoint e conferma per impostarlo qui."#, Alcuni consigli prima di cominciare: -MOLTO IMPORTANTE: Per impostare il tuo punto di rinascita scrivi /waypoint - - -nella chat. - - -Ciò può essere fatto anche se sei già morto! - - - Premi F1 per vedere i comandi chiave disponibili. @@ -268,7 +265,7 @@ Gettali via cliccandoci una volta sopra e una volta fuori dall’inventario. Le notti possono essere molto buie in Veloren. -Accendi la tua lanterna scrivendo /lantern nella chat. +Accendi la tua lanterna premendo 'G' @@ -280,19 +277,47 @@ Vuoi sbloccare il cursore per chiudere questa finestra? Premi TAB! Goditi il tuo soggiorno nel Mondo di Veloren."#, -"hud.temp_quest_headline": r#"Please, help us Traveller!"#, -"hud.temp_quest_text": r#"Dungeons filled with evil cultists -have emerged all around our peaceful towns! +"hud.temp_quest_headline": r#"Perfavore, aiutaci avventuriero!"#, +"hud.temp_quest_text": r#"Dungeon pieni di cultisti malvagi +sono emersi tutto intorno alle nostre pacifiche cittadine! -Gather some company, stack up on food -and defeat their vile leaders and acolytes. +Raduna un equipaggio, rifornisciti di viveri +e sconfiggi i loro vili leader e accoliti. -Maybe you can even obtain one of their -magically infused items?"#, +Forse potresti persino ottenere uno dei loro +oggetti infusi di magia?"#, + + // Inventory + "hud.bag.inventory": "Inventario di {playername}", + "hud.bag.stats_title": "Statistiche di {playername}", + "hud.bag.exp": "Esperienza", + "hud.bag.armor": "Armatura", + "hud.bag.stats": "Statistiche", + "hud.bag.head": "Testa", + "hud.bag.neck": "Collo", + "hud.bag.tabard": "Cotta di maglia", + "hud.bag.shoulders": "Spalle", + "hud.bag.chest": "Torace", + "hud.bag.hands": "Mani", + "hud.bag.lantern": "Lanterna", + "hud.bag.belt": "Cintura", + "hud.bag.ring": "Anello", + "hud.bag.back": "Schiena", + "hud.bag.legs": "Gambe", + "hud.bag.feet": "Piedi", + "hud.bag.mainhand": "Mano Principale", + "hud.bag.offhand": "Mano Secondaria", + + + // Map and Questlog + "hud.map.map_title": "Mappa", + "hud.map.qlog_title": "Missioni", + + // Settings "hud.settings.general": "Generale", "hud.settings.none": "Nessuno", "hud.settings.press_behavior.toggle": "Attiva/Disattiva", @@ -313,12 +338,15 @@ magically infused items?"#, "hud.settings.cumulated_damage": "Danno Nemico (Cumulativo)", "hud.settings.incoming_damage": "Danno Giocatore (Singolo)", "hud.settings.cumulated_incoming_damage": "Danno Giocatore (Cumulativo)", + "hud.settings.speech_bubble": "Fumetto", + "hud.settings.speech_bubble_dark_Spmode": "Fumetto Modalità Scura", + "hud.settings.speech_bubble_icon": "Icona Fumetto", "hud.settings.energybar_numbers": "Numeri Barra dell’Energia", "hud.settings.values": "Valori", "hud.settings.percentages": "Percentuali", "hud.settings.chat": "Chat", "hud.settings.background_transparency": "Trasparenza dello Sfondo", - "hud.settings.none": "Nessuno", + "hud.settings.chat_character_name": "Nome dei personaggi in chat", @@ -327,12 +355,18 @@ magically infused items?"#, "hud.settings.zoom_sensitivity": "Sensibilità Zoom", "hud.settings.invert_scroll_zoom": "Zoom Invertito", "hud.settings.invert_mouse_y_axis": "Asse Y del Mouse Invertito", + "hud.settings.enable_mouse_smoothing": "Camera Smoothing", "hud.settings.free_look_behavior": "Comportamento Visuale Libera", + "hud.settings.auto_walk_behavior": "Comportamento Camminata Automatica", + "hud.settings.stop_auto_walk_on_input": "Interrompere Camminata Automatica dopo movimento", + "hud.settings.view_distance": "Distanza Oggetto", + "hud.settings.sprites_view_distance": "Distanza Sprite", + "hud.settings.figures_view_distance": "Distanza Entità", "hud.settings.maximum_fps": "FPS Massimi", "hud.settings.fov": "Campo Visivo (gradi)", "hud.settings.gamma": "Gamma", @@ -352,145 +386,98 @@ magically infused items?"#, "hud.settings.sound_effect_volume": "Volume Effetti Sonori", "hud.settings.audio_device": "Dispositivo Audio", - - - - // Control list - "hud.settings.control_names": r#"Cursore Libero -Attiva/Disattiva Finestra di Aiuto -Attiva/Disattiva Interfaccia -Attiva/Disattiva FPS e Informazioni di Debug -Scatta Screenshot -Attiva/Disattiva Nomi -Attiva/Disattiva Schermo Intero - - -Movimento in Avanti -Movimento a Sinistra -Movimento a Destra -Movimento all’Indietro - -Salto - -Aliante - -Schivata - -Rotolata - -Scalata - -Discesa - -Camminata Automatica - -Riporre/Sfoderare Armi - -Mettere/Rimuovere Elmo - -Sedersi - -Cavalcatura - -Interagire - - -Attacco Base -Attacco Secondario/Parata/Mira - - -Slot 1 Barra delle Abilità -Slot 2 Barra delle Abilità -Slot 3 Barra delle Abilità -Slot 4 Barra delle Abilità -Slot 5 Barra delle Abilità -Slot 6 Barra delle Abilità -Slot 7 Barra delle Abilità -Slot 8 Barra delle Abilità -Slot 9 Barra delle Abilità -Slot 10 Barra delle Abilità - - -Menù di Pausa -Impostazioni -Social -Mappa -Libro degli Incantesimi -Personaggio -Diario delle Missioni -Borsa - - - -Invia Messaggio nella Chat -Scorrimento della Chat - -Camera Libera - - - -Comandi della Chat: - -/alias [Name] - Cambia il tuo Nome nella Chat -/tp [Name] - Ti teleporta da un altro giocatore -/jump - Devia la tua posizione -/goto - Ti teleporta in una posizione -/kill - Suicidati -/pig - Fai apparire un maiale PNG -/wolf - Fai apparire un lupo PNG -/help - Mostra comandi della Chat"#, - - - - - "hud.social": "Social", + "hud.settings.awaitingkey": "Premi un tasto...", + + "hud.social": "Sociale", "hud.social.online": "Online", "hud.social.friends": "Amici", "hud.social.not_yet_available": "Non ancora disponibile", "hud.social.faction": "Fazione", "hud.social.play_online_fmt": "{nb_player} giocatore/i online", - - - - - "hud.spell": "Incantesimo", - "hud.free_look_indicator": "Visuale Libera Attiva", + "hud.spell": "Incantesimi", + + "hud.free_look_indicator": "Visuale libera attiva", + "hud.auto_walk_indicator": "Camminata automatica attiva", + /// End HUD section + + /// Start GameInput section - - - - - - - + "gameinput.primary": "Attacco Base", + "gameinput.secondary": "Attacco Secondario/Parata/Mira", + "gameinput.slot1": "Barra delle Abilità 1", + "gameinput.slot2": "Barra delle Abilità 2", + "gameinput.slot3": "Barra delle Abilità 3", + "gameinput.slot4": "Barra delle Abilità 4", + "gameinput.slot5": "Barra delle Abilità 5", + "gameinput.slot6": "Barra delle Abilità 6", + "gameinput.slot7": "Barra delle Abilità 7", + "gameinput.slot8": "Barra delle Abilità 8", + "gameinput.slot9": "Barra delle Abilità 9", + "gameinput.slot10": "Barra delle Abilità 10", + "gameinput.swaploadout": "Scambia Equipaggiamento", + "gameinput.togglecursor": "Attiva/Disattiva Cursore", + "gameinput.help": "Attiva/Disattiva Finestra d'Aiuto", + "gameinput.toggleinterface": "Attiva/Disattiva Interfaccia", + "gameinput.toggledebug": "Attiva/Disattiva FPS e Informazioni di Debug", + "gameinput.screenshot": "Scatta uno Screenshot", + "gameinput.toggleingameui": "Attiva/Disattiva Nomi", + "gameinput.fullscreen": "Attiva/Disattiva Fullscreen", + "gameinput.moveforward": "Movimento in Avanti", + "gameinput.moveleft": "Movimento a Sinistra", + "gameinput.moveright": "Movimento a Destra", + "gameinput.moveback": "Movimento Indietro", + "gameinput.jump": "Salto", + "gameinput.glide": "Aliante", + "gameinput.roll": "Rotolata", + "gameinput.climb": "Scalata", + "gameinput.climbdown": "Scalata in Giù", + "gameinput.wallleap": "Salto al Muro", + "gameinput.togglelantern": "Attiva/Disattiva Lanterna", + "gameinput.mount": "Cavalcatura", + "gameinput.enter": "Invio", + "gameinput.command": "Comando", + "gameinput.escape": "Escape", + "gameinput.map": "Mappa", + "gameinput.bag": "Borsa", + "gameinput.social": "Sociale", + "gameinput.sit": "Sedersi", + "gameinput.spellbook": "Incantesimi", + "gameinput.settings": "Opzioni", + "gameinput.respawn": "Rinascita", + "gameinput.charge": "Carica", + "gameinput.togglewield": "Sfodera/Rinfodera Arma", + "gameinput.interact": "Interagire", + "gameinput.freelook": "Visuale Libera", + "gameinput.autowalk": "Camminata Automatica", + "gameinput.dance": "Danza", + + /// End GameInput section + /// Start character selection section + "char_selection.loading_characters": "Caricamento Personaggio...", "char_selection.delete_permanently": "Eliminare permanente questo Personaggio?", + "char_selection.deleting_character": "Cancellazione Personaggio...", "char_selection.change_server": "Cambia Server", "char_selection.enter_world": "Unisciti al Mondo", "char_selection.logout": "Disconnettiti", "char_selection.create_new_charater": "Crea un nuovo Personaggio", + "char_selection.creating_character": "Creazione Personaggio...", "char_selection.character_creation": "Creazione Personaggio", - - - "char_selection.human_default": "Umano Predefinito", "char_selection.level_fmt": "Livello {level_nb}", - "char_selection.uncanny_valley": "Valle Perturbante", + "char_selection.uncanny_valley": "Regione Selvaggia", "char_selection.plains_of_uncertainty": "Pianure dell'Incertezza", "char_selection.beard": "Barba", "char_selection.hair_style": "Stile Capelli", "char_selection.hair_color": "Colore Capelli", - "char_selection.chest_color": "Colore Torace", "char_selection.eye_color": "Colore Occhi", "char_selection.skin": "Pelle", - "char_selection.eyebrows": "Sopracciglia", + "char_selection.eyeshape": "Dettagli Occhi", "char_selection.accessories": "Accessori", - - - + "char_selection.create_info_name": "Il tuo personaggio necessita di un nome!", /// End chracter selection section @@ -516,26 +503,97 @@ Vitalità Volontà "#, + /// End character window section - - - - - - - /// Start character window section - - - - - - /// Start Escape Menu Section - "esc_menu.logout": "Disconnettiti", - "esc_menu.quit_game": "Esci dal Gioco", - /// End Escape Menu Section +/// Start Escape Menu Section + "esc_menu.logout": "Disconnettiti", + "esc_menu.quit_game": "Esci dal Gioco", + /// End Escape Menu Section }, - vector_map: { + vector_map: { + "npc.speech.villager_under_attack": [ + "Aiuto, sono sotto attacco!", + "Aiuto! Sono sotto attacco!", + "Ouch! Sono sotto attacco!", + "Ouch! Sono sotto attacco! Aiuto!", + "Aiutatemi! Sono sotto attacco!", + "Sono sotto attacco! Aiuto!", + "Sono sotto attacco! Aiutatemi!", + "Aiuto!", + "Aiuto! Aiuto!", + "Aiuto! Aiuto! Aiuto!", + "Sono sotto attacco!", + "AAAHHH! Sono sotto attacco!", + "AAAHHH! Sono sotto attacco! Aiuto!", + "Aiuto! Siamo sotto attacco!", + "Aiuto! Assassino!", + "Aiuto! C'è un assassino in circolazione!", + "Aiuto! Stanno cercando di uccidermi!", + "Guardie, sono sotto attacco!", + "Guardie! Sono sotto attacco!", + "Sono sotto attacco! Guardie!", + "Aiuto! Guardie! Sono sotto attacco!", + "Guardie! Venite presto!", + "Guardie! Guardie!", + "Guardie! C'è un furfante che mi sta attaccando!", + "Guardie, ammazzate questa disonesta canaglia!", + "Guardie! C'è un assassino!", + "Guardie! Aiutatemi!", + "Non la farai franca dopo questa! Guardie!", + "Mostro!", + "Aiutatemi!", + "Aiuto! Per favore!", + "Ouch! Guardie! Aiuto!", + "Stanno venendo per me!", + "Aiuto! Aiuto! Sto venendo represso!", + "Ah, ora vediamo la violenza insita nel sistema.", + "Non è altro che un graffio!", + "Smettila!", + "Che cosa ti ho mai fatto?!", + "Per favore smettila di attaccarmi!", + "Hey! Attento a dove punti quella cosa!", + "Odioso miserabile, vattene via!", + "Smettila! Vai via!", + "Adesso mi stai facendo arrabbiare!", + "Oi! Chi pensi di essere?!", + "Avrò la tua testa per ciò!", + "Fermati, ti prego! Non ho con me nulla di valore!", + "Ti metterò contro mio fratello, è più grande di me!", + "Nooo, glielo dico alla mamma!", + "Che tu sia maledetto!", + "Ti prego di non farlo.", + "Non è stato molto carino!", + "La tua arma funziona, puoi metterla via ora!", + "Risparmiami!", + "Ti prego, ho una famiglia!", + "Sono troppo giovane per morire!", + "Possiamo parlarne?", + "La violenza non è mai la risposta!", + "Oggi sta per diventare un giorno molto brutto...", + "Hey, ha fatto male!", + "Eek!", + "Quanto rude!", + "Fermati, te ne prego!", + "Che ti prenda il vaiolo!", + "Non è divertente.", + "Come ti permetti?!", + "La pagherai per questo!", + "Continua così e te ne pentirai!", + "Non costringermi a farti del male!", + "Ci deve essere un malinteso!", + "Non hai bisogno di fare così!", + "Vattene via, mostro!", + "Quello ha fatto veramente male!", + "Perché mai faresti una cosa del genere?", + "Per gli spiriti, fermati!", + "Mi devi aver confuso con qualcun altro!", + "Non mi merito tutto ciò!", + "Per favore non lo rifare.", + "Guardie, gettate questo mostro nel lago!", + "Sguinzaglierò il mio tarrasque su di te!", + ], } + ) diff --git a/assets/voxygen/i18n/pt_BR.ron b/assets/voxygen/i18n/pt_BR.ron index f6b5b1f694..453916c477 100644 --- a/assets/voxygen/i18n/pt_BR.ron +++ b/assets/voxygen/i18n/pt_BR.ron @@ -123,12 +123,13 @@ https://account.veloren.net."#, "main.login.failed_sending_request": "Requisição ao servidor de autenticação falhou", "main.login.invalid_character": "O personagem selecionado é inválido", "main.login.client_crashed": "Cliente abortou", + "main.login.not_on_whitelist": "Você precisa ser permitido por um Admin para ingressar", /// End Main screen section /// Start HUD Section - "hud.do_not_show_on_startup": "Nâo mostrar no início", + "hud.do_not_show_on_startup": "Não mostrar no início", "hud.show_tips": "Mostrar dicas", "hud.quests": "Missões", "hud.you_died": "Você Morreu", @@ -139,6 +140,18 @@ https://account.veloren.net."#, "hud.press_key_to_toggle_keybindings_fmt": "Pressione {key} para mostrar/ocultar teclas mapeadas", "hud.press_key_to_toggle_debug_info_fmt": "Pressione {key} para mostrar/ocultar informações de depuração", + // Chat outputs + "hud.chat.online_msg": "[{name}] está online.", + "hud.chat.offline_msg": "{name} está offline.", + "hud.chat.loot_msg": "Você pegou [{item}]", + "hud.chat.loot_fail": "Seu Inventário está cheio!", + "hud.chat.goodbye": "Tchau!", + "hud.chat.connection_lost": "Conexão perdida. Chutando em {time} segundos.", + + // SCT outputs + "hud.sct.experience": "{amount} Exp", + "hud.sct.block": "BLOQUEADO", + // Respawn message "hud.press_key_to_respawn": r#"Pressione {key} para renascer na última fogueira visitada."#, @@ -175,6 +188,19 @@ Quer liberar o cursor do mouse para fechar esta janela? Pressione TAB! Aproveite a sua estadia no Mundo de Veloren."#, +"hud.temp_quest_headline": r#"Por favor, nos ajude Viajante!"#, +"hud.temp_quest_text": r#"As Cavernas estão repletas de cultistas maus +que emergiram para nossos pacíficos vilarejos! + + +Consiga companheiros, junte comida +e derrote seus líderes vis e acólitos. + + +Talvez você até consiga obter um dos seus +itens magicamente modificados?"#, + + // Inventory "hud.bag.inventory": "Inventário de {playername}", @@ -223,12 +249,15 @@ Aproveite a sua estadia no Mundo de Veloren."#, "hud.settings.cumulated_damage": "Dano Acumulado", "hud.settings.incoming_damage": "Dano Recebido", "hud.settings.cumulated_incoming_damage": "Dano Recebido Acumulado", - "hud.settings.speech_bubble_dark_mode": "Balão de fala no modo escuro", + "hud.settings.speech_bubble": "Balão de Fala", + "hud.settings.speech_bubble_dark_mode": "Balão de Fala no modo escuro", + "hud.settings.speech_bubble_icon": "Ícone do Balão de Fala", "hud.settings.energybar_numbers": "Números da Barra de energia", "hud.settings.values": "Valores", "hud.settings.percentages": "Porgentavens", "hud.settings.chat": "Chat", "hud.settings.background_transparency": "Transparência de Fundo", + "hud.settings.chat_character_name": "Nomes de Personagem no Chat", "hud.settings.pan_sensitivity": "Sensibilidade de Rotação", "hud.settings.zoom_sensitivity": "Sensibilidade do Zoom", @@ -236,6 +265,8 @@ Aproveite a sua estadia no Mundo de Veloren."#, "hud.settings.invert_mouse_y_axis": "Inverter eixo Y do Mouse", "hud.settings.enable_mouse_smoothing": "Suavização da Câmera", "hud.settings.free_look_behavior": "Comportamento de Câmera Livre", + "hud.settings.auto_walk_behavior": "Comportamento do caminhar automático", + "hud.settings.stop_auto_walk_on_input": "Parar caminhar automático em caso de movimento", "hud.settings.view_distance": "Alcance de visão", "hud.settings.sprites_view_distance": "Distância de visão dos Sprites", @@ -320,6 +351,9 @@ Aproveite a sua estadia no Mundo de Veloren."#, "gameinput.togglewield": "Alternar Empunhadura", "gameinput.interact": "Interagir", "gameinput.freelook": "Câmera Livre", + "gameinput.autowalk": "Caminhar Automático", + "gameinput.dance": "Dançar", + /// End GameInput section @@ -340,7 +374,8 @@ Aproveite a sua estadia no Mundo de Veloren."#, "char_selection.uncanny_valley": "Vale Misterioso", "char_selection.plains_of_uncertainty": "Planícies da Incerteza", "char_selection.beard": "Barba", - "char_selection.hair_style": "Estilo do cabelo", + "char_selection.hair_style": "Estilo do Cabelo", + "char_selection.hair_color": "Cor do Cabelo", "char_selection.eye_color": "Cor de olho", "char_selection.skin": "Pele", "char_selection.eyeshape": "Detalhe do olho", @@ -359,8 +394,6 @@ Aptidão Força de Vontade "#, - - /// Start character window section @@ -407,7 +440,50 @@ Força de Vontade "Estão atrás de mim!", "Ajuda! Ajuda! Estou sendo repreendido", "Ah, agora vemos a violência inerente ao sistema.", - "Só um arranhão!" + "Só um arranhão!", + "Pare com isso!", + "O que eu fiz para você?!", + "Por favor, pare de me atacar!", + "Hey! Cuida pra onde você aponta essa coisa!", + "Desgraçado hediondo, vou acabar com você!", + "Pare com isso! Vá embora!", + "Você está me deixando louco!", + "Ow! Quem você pensa que é?!", + "Arrancarei sua cabeça por isso!", + "Pare, por favor! Não levo nada de valor!", + "Vou mandar meu irmão em você, ele é maior que eu!", + "Nãooo, Vou contar pra minha mãe!", + "Te amaldiçoo!", + "Por favor não faça isso.", + "Isso não foi muito legal!", + "Sua arma funciona, pode guardar ela agora!", + "Me poupe!", + "Por favor, tenho uma família!", + "Sou muito jovem para morrer!", + "Podemos conversar sobre isso?", + "Violência nunca é a resposta!", + "Hoje foi um péssimo dia...", + "Ei, isso dói!", + "Eek!", + "Que rude!", + "Pare, eu imploro!", + "Que você adoeça!", + "Isso não é engraçado.", + "Como ousa?!", + "Você pagará por isso!", + "Continue assim e irá se arrepender!", + "Não me faça te machucar!", + "Deve haver algum engano!", + "Não precisa fazer isso!", + "Morre, Diabo!", + "Isso Dói!", + "Porque você faria isso?", + "Pelos espíritos, Pare!", + "Você deve ter me confundido com alguém!", + "Eu não mereço isso!", + "Por favor, não faça isso novamente.", + "Guardas, joguem este monstro no lago!", + "Vou mandar meu tarrasque em você!", ], } ) diff --git a/assets/voxygen/i18n/tr_TR.ron b/assets/voxygen/i18n/tr_TR.ron index 5208cc0354..87da2a0ecb 100644 --- a/assets/voxygen/i18n/tr_TR.ron +++ b/assets/voxygen/i18n/tr_TR.ron @@ -64,12 +64,12 @@ VoxygenLocalization( "common.back": "Geri", "common.create": "Oluştur", "common.okay": "Tamam", + "common.accept": "Kabul Et", "common.disclaimer": "Uyarı", "common.cancel": "İptal Et", "common.none": "Yok", "common.error": "Hata", "common.fatal_error": "Ölümcül hata", - "common.accept": "Kabul Et", // Message when connection to the server is lost "common.connection_lost": r#"Bağlantı koptu! @@ -95,6 +95,7 @@ Sunucu yeniden mi başladı? /// Start Main screen section "main.connecting": "Bağlanılıyor", "main.creating_world": "Dünya oluşturuluyor", + "main.tip": "İpucu:", // Welcome notice that appears the first time Veloren is started "main.notice": r#"Veloren Alfa sürümüne hoşgeldin! @@ -138,6 +139,7 @@ bir hesap oluşturabilirsin."#, "main.login.failed_sending_request": "Kimlik doğrulama sunucusuna istek gönderilemedi", "main.login.invalid_character": "Seçilen karakter geçersiz", "main.login.client_crashed": "İstemci çöktü", + "main.login.not_on_whitelist": "Sunucuya girmek için bir Yönetici tarafından beyaz listeye eklenmen gerekiyor", /// End Main screen section @@ -154,6 +156,19 @@ bir hesap oluşturabilirsin."#, "hud.press_key_to_toggle_keybindings_fmt": "Kontrolleri açmak veya kapamak için {key}'e bas", "hud.press_key_to_toggle_debug_info_fmt": "Hata ayıklama bilgilerini açmak veya kapamak için {key}'e bas", + + // Chat outputs + "hud.chat.online_msg": "[{name}] çevrimiçi oldu.", + "hud.chat.offline_msg": "{name} çevrimdışı oldu.", + "hud.chat.loot_msg": "[{item}] topladın.", + "hud.chat.loot_fail": "Envanterin dolu!", + "hud.chat.goodbye": "Hoşçakal!", + "hud.chat.connection_lost": "Bağlantı koptu. {time} saniye içinde sunucudan atılacaksın.", + + // SCT outputs + "hud.sct.experience": "{amount} Tecrübe", + "hud.sct.block": "BLOKLANDI", + // Respawn message "hud.press_key_to_respawn": r#"Ziyaret ettiğin en son kamp ateşinde yeniden doğmak için {key}'e bas."#, @@ -254,13 +269,16 @@ magically infused items?"#, "hud.settings.cumulated_damage": "Toplam Verilen Hasarı Göster", "hud.settings.incoming_damage": "Alınan Hasarı Tek Tek Göster", "hud.settings.cumulated_incoming_damage": "Toplam Alınan Hasarı Göster", - "hud.settings.speech_bubble_dark_mode": "Konuşma balonu için karanlık tema kullan", + "hud.settings.speech_bubble": "Konuşma balonu", + "hud.settings.speech_bubble_dark_mode": "Konuşma balonunda karanlık tema kullan", + "hud.settings.speech_bubble_icon": "Konuşma balonunda ikon göster", "hud.settings.energybar_numbers": "Enerji çubuğu değerleri", "hud.settings.values": "Sayılar", "hud.settings.percentages": "Yüzdeler", "hud.settings.chat": "Sohbet", "hud.settings.background_transparency": "Arkaplan Şeffaflığı", - "hud.settings.none": "Yok", + "hud.settings.chat_character_name": "Sohbette karakter isimlerini göster", + "hud.settings.loading_tips": "Yükleme ekranı ipuçları", "hud.settings.pan_sensitivity": "Kaydırma Hassaslığı", "hud.settings.zoom_sensitivity": "Büyütme Hassaslığı", @@ -268,6 +286,9 @@ magically infused items?"#, "hud.settings.invert_mouse_y_axis": "Fare Y eksenini ters çevir", "hud.settings.enable_mouse_smoothing": "Kamera kontrolünü yumuşat", "hud.settings.free_look_behavior": "Serbest bakış davranışı", + "hud.settings.auto_walk_behavior": "Otomatik yürüme davranışı", + "hud.settings.stop_auto_walk_on_input": r#"Otomatik yürüyüşü hareket +edince kapat"#, "hud.settings.view_distance": "Görüş Mesafesi", "hud.settings.sprites_view_distance": "Sprite Görüş Mesafesi", @@ -285,6 +306,8 @@ magically infused items?"#, "hud.settings.save_window_size": "Pencere boyutunu kaydet", "hud.settings.awaitingkey": "Bir tuşa bas...", + "hud.settings.unbound": "Atanmamış", + "hud.settings.reset_keybinds": "Varsayılana döndür", "hud.settings.music_volume": "Müzik Sesi", "hud.settings.sound_effect_volume": "Efekt Sesi", @@ -297,9 +320,17 @@ magically infused items?"#, "hud.social.faction": "Klan", "hud.social.play_online_fmt": "{nb_player} oyuncu çevrimiçi", + "hud.crafting": "Üretim", + "hud.crafting.recipes": "Tarifler", + "hud.crafting.ingredients": "Malzemeler:", + "hud.crafting.craft": "Üret", + "hud.crafting.tool_cata": "Gerektiriyor:", + "hud.spell": "Büyü", "hud.free_look_indicator": "Serbest bakış açık", + "hud.auto_walk_indicator": "Otomatik yürüme açık", + /// End HUD section @@ -351,6 +382,8 @@ magically infused items?"#, "gameinput.togglewield": "Kuşan/koy", "gameinput.interact": "Etkileşim", "gameinput.freelook": "Serbest Bakış", + "gameinput.autowalk": "Otomatik Yürüyüş", + "gameinput.dance": "Dans et", /// End GameInput section @@ -365,7 +398,7 @@ silmek istediğinden emin misin?"#, "char_selection.logout": "Çıkış yap", "char_selection.create_new_charater": "Yeni Karakter Oluştur", "char_selection.creating_character": "Karakter oluşturuluyor...", - "char_selection.character_creation": "Karakter Oluşumu", + "char_selection.character_creation": "Karakter Oluşturma", "char_selection.human_default": "İnsan Varsayılanı", "char_selection.level_fmt": "Seviye {level_nb}", @@ -374,11 +407,9 @@ silmek istediğinden emin misin?"#, "char_selection.beard": "Sakal", "char_selection.hair_style": "Saç Stili", "char_selection.hair_color": "Saç Rengi", - "char_selection.chest_color": "Göğüs Rengi", "char_selection.eye_color": "Göz Rengi", "char_selection.skin": "Deri", "char_selection.eyeshape": "Göz Detayları", - "char_selection.eyebrows": "Kaşlar", "char_selection.accessories": "Aksesuarlar", "char_selection.create_info_name": "Karakterinin bir isme ihtiyacı var!", @@ -393,6 +424,8 @@ silmek istediğinden emin misin?"#, Hareket gücü İrade gücü + +Koruma "#, @@ -406,6 +439,24 @@ Hareket gücü }, vector_map: { + "loading.tips": [ + "'G'ye basarak fenerini yak.", + "'F1'e basarak bütün kontrolleri görebilirsin.", + "'/say' veya '/s' yazarak sadece hemen yanındaki oyuncularla konuşabilirsin.", + "'/region' veya '/r' yazarak sadece bir kaç yüz blok içindeki oyuncularla konuşabilirsin.", + "Özel bir mesaj göndermek için '/tell' ve sonra bir oyuncu ismi ile mesajını yaz.", + "Aynı seviyedeki NPCler farklı zorluklara sahip olabilir.", + "Yemek, sandık ve diğer ganimetler için yere bak!", + "Envanterin yemekle mi dolu? Onları kullanarak daha iyi yemek yapmaya çalış!", + "Ne yapabileceğini merak mı ediyorsun? Zindanlar haritada kahverengi bölgeler olarak işaretlenmiştir!", + "Grafikleri sistemin için ayarlamayı unutma. 'N'e basarak ayarları aç.", + "Başkalarıyla oynamak eğlencelidir! 'O'ya basarak kimlerin çevirimiçi olduğunu gör.", + "Can barının yanında kurukafa olan bir NPC senden hayli bir güçlüdür.", + "'J'ye basarak dans et. Parti!", + "'L-Shift'e basarak Planörünü aç ve gökyüzünü fethet.", + "Veloren hala Pre-Alpha'da. Onu geliştirmek için her gün elimizden geleni yapıyoruz!", + "Geliştirme Takımına katılmak istiyorsan veya sadece sohbet etmek istiyorsan Discord sunucumuza katıl.", + ], "npc.speech.villager_under_attack": [ "Saldırı altındayım, yardım edin!", "Saldırı altındayım! Yardım edin!", @@ -442,7 +493,7 @@ Hareket gücü "Benim için geliyorlar!", "Yardım edin! Yardım edin! Baskı altındayım!", "Ah, artık sistemin doğasında var olan şiddeti görüyoruz.", - "Bu benim için bir çizik bile değil!" + "Bu bana göre bir çizik bile değil!", ], } ) diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 68c68ffc4f..5f6e0daa18 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -191,7 +191,7 @@ Tool(Hammer(BasicHammer)): VoxTrans( "voxel.weapon.hammer.rusty_2h", (2.0, -1.0, 0.0), (-135.0, 90.0, 0.0), 1.1, - ), + ), // Staffs Tool(Staff(BasicStaff)): VoxTrans( "voxel.weapon.staff.wood-fire", @@ -720,6 +720,10 @@ "voxel.armor.back.admin", (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, ), + Armor(Back(DungPurp0)): VoxTrans( + "voxel.armor.back.dung_purp-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), // Rings Armor(Ring(Ring0)): Png( "element.icons.ring-0", @@ -743,24 +747,30 @@ ), // Consumables Consumable(Apple): - VoxTrans( + Png( "element.icons.item_apple", - (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 1.0, ), Consumable(Coconut): Png( "element.icons.item_coconut", ), + Consumable(PotionMed): VoxTrans( + "voxel.object.potion_red", + (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, + ), Consumable(PotionMinor): VoxTrans( "voxel.object.potion_red", - (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.8, + (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.5, + ), + Consumable(PotionLarge): VoxTrans( + "voxel.object.potion_red", + (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.9, ), Consumable(PotionExp): VoxTrans( "voxel.object.potion_turq", (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.8, ), - Consumable(Cheese): VoxTrans( + Consumable(Cheese): Png( "element.icons.item_cheese", - (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 0.9, ), Consumable(Potion): VoxTrans( "voxel.object.potion_red", @@ -776,18 +786,32 @@ ), Consumable(VeloriteFrag): VoxTrans( "voxel.sprite.velorite.velorite_1", - (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, + (0.0, 0.0, 0.0), (-50.0, 40.0, 20.0), 0.8, ), + Consumable(AppleShroomCurry): Png( + "element.icons.item_apple_curry", + ), + Consumable(AppleStick): Png( + "element.icons.item_apple_stick", + ), + Consumable(MushroomStick): Png( + "element.icons.item_shroom_stick", + ), + // Throwables Throwable(Bomb): VoxTrans( "voxel.object.bomb", - (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, + (0.0, 0.5, 0.0), (-50.0, 40.0, 20.0), 0.8, ), Throwable(TrainingDummy): VoxTrans( "voxel.object.training_dummy", (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, ), // Ingredients + Ingredient(CraftsmanHammer): VoxTrans( //TODO This should be a 1h hammer! + "voxel.weapon.hammer.craftsman", + (1.0, 1.0, 0.0), (-135.0, 90.0, 0.0), 1.0, + ), Ingredient(Flower): VoxTrans( "voxel.sprite.flowers.flower_red_2", (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, @@ -796,6 +820,27 @@ "voxel.sprite.grass.grass_long_5", (0.0, 0.0, 0.0), (-90.0, 50.0, 0.0), 1.0, ), + Ingredient(Stones): VoxTrans( + "voxel.sprite.rocks.rock-0", + (0.0, 0.0, 0.0), (-50.0, 40.0, 20.0), 0.8, + ), + Ingredient(Twigs): VoxTrans( + "voxel.sprite.twigs.twigs-0", + (0.0, 0.0, 0.0), (-20.0, 10.0, 20.0), 0.9, + ), + Ingredient(LeatherScraps): Png( + "element.icons.item_leather0", + ), + Ingredient(ShinyGem): Png( + "element.icons.gem", + ), + Ingredient(MortarPestle): Png( + "element.icons.item_mortarpestlecoco", + ), + Ingredient(EmptyVial): VoxTrans( + "voxel.object.potion_empty", + (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.8, + ), // Debug Items Tool(Debug(Boost)): VoxTrans( "voxel.weapon.tool.broom_belzeshrub_purple", diff --git a/assets/voxygen/voxel/armor/back/dung_purp-0.vox b/assets/voxygen/voxel/armor/back/dung_purp-0.vox new file mode 100644 index 0000000000..1c82139479 --- /dev/null +++ b/assets/voxygen/voxel/armor/back/dung_purp-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6589aee7dcf964a185725824c2fea58e419d734230e5d22fcec789a8a20e5224 +size 1672 diff --git a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron index 5165820bc9..2cd68d083e 100644 --- a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -11,6 +11,10 @@ Admin: ( vox_spec: ("armor.back.admin", (-5.0, -1.0, -11.0)), color: None + ), + DungPurp0: ( + vox_spec: ("armor.back.dung_purp-0", (-5.0, -1.0, -14.0)), + color: None ), }, )) diff --git a/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron b/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron index 3ca33ada52..3efea95da9 100644 --- a/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron +++ b/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron @@ -176,6 +176,10 @@ vox_spec: ("weapon.hammer.rusty_2h", (-2.5, -5.5, -4.0)), color: None ), + /*Dagger(Craftsman): ( //TODO This should be a 1h hammer! + vox_spec: ("weapon.hammer.craftsman", (-2.0, -5.0, -5.5)), + color: None + ),*/ // Daggers Dagger(BasicDagger): ( vox_spec: ("weapon.dagger.dagger_rusty", (-1.5, -3.0, -3.0)), diff --git a/assets/voxygen/voxel/object/potion_empty.vox b/assets/voxygen/voxel/object/potion_empty.vox new file mode 100644 index 0000000000..4f4bd99856 --- /dev/null +++ b/assets/voxygen/voxel/object/potion_empty.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7d2dc44a3bd0035ea3ef934cd9420360dd118ada159ac18a187fc0308a246f +size 1400 diff --git a/assets/voxygen/voxel/sprite/gem/gem_blue.vox b/assets/voxygen/voxel/sprite/gem/gem_blue.vox new file mode 100644 index 0000000000..e8ab838a40 --- /dev/null +++ b/assets/voxygen/voxel/sprite/gem/gem_blue.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b00fd0d2018c4923134e675e0ccb5db8e0f0f8da5387a7fca4cf299b07a300e +size 1184 diff --git a/assets/voxygen/voxel/sprite/gem/gem_green.vox b/assets/voxygen/voxel/sprite/gem/gem_green.vox new file mode 100644 index 0000000000..285d6170eb --- /dev/null +++ b/assets/voxygen/voxel/sprite/gem/gem_green.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7238b7eb2d8a2ba98afdf3cfd7eeadf5a5832a6fc0f285e00008487553309f91 +size 1208 diff --git a/assets/voxygen/voxel/sprite/gem/gem_red.vox b/assets/voxygen/voxel/sprite/gem/gem_red.vox new file mode 100644 index 0000000000..6765230e7b --- /dev/null +++ b/assets/voxygen/voxel/sprite/gem/gem_red.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61c11c616e3ed4d011ead74acf1273823936a85deae7b3db214a2aa6ab54ee72 +size 1336 diff --git a/assets/voxygen/voxel/sprite/rocks/rock-0.vox b/assets/voxygen/voxel/sprite/rocks/rock-0.vox new file mode 100644 index 0000000000..bd0c85c9be --- /dev/null +++ b/assets/voxygen/voxel/sprite/rocks/rock-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:219f3cfd7e310284e17397a59441cfc79d27281ca09bea8a4740eee4173dab89 +size 1336 diff --git a/assets/voxygen/voxel/sprite/rocks/rock-1.vox b/assets/voxygen/voxel/sprite/rocks/rock-1.vox new file mode 100644 index 0000000000..ca13137003 --- /dev/null +++ b/assets/voxygen/voxel/sprite/rocks/rock-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c9365b3ddee1c873a9dbbe0a9e5c86280561562e14cda6540235d9d58bc58d0 +size 1936 diff --git a/assets/voxygen/voxel/sprite/rocks/rock-2.vox b/assets/voxygen/voxel/sprite/rocks/rock-2.vox new file mode 100644 index 0000000000..42466dd089 --- /dev/null +++ b/assets/voxygen/voxel/sprite/rocks/rock-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64e5992e0833d6f22ffb833e83ee967b1edb55d52414d45ce3afada7b0fc8e82 +size 1368 diff --git a/assets/voxygen/voxel/sprite/twigs/twigs-0.vox b/assets/voxygen/voxel/sprite/twigs/twigs-0.vox new file mode 100644 index 0000000000..e91efb6c3b --- /dev/null +++ b/assets/voxygen/voxel/sprite/twigs/twigs-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd6c3dedba6acda92de5d70400ec8fe886f73015f3a2f088c7907ecbf57a6319 +size 1144 diff --git a/assets/voxygen/voxel/sprite/twigs/twigs-1.vox b/assets/voxygen/voxel/sprite/twigs/twigs-1.vox new file mode 100644 index 0000000000..4584d748e2 --- /dev/null +++ b/assets/voxygen/voxel/sprite/twigs/twigs-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7829f29cd6bc68c9d9f5a04ab7c9feefb73031f438b777ddd81d1f67415c7f8 +size 1120 diff --git a/assets/voxygen/voxel/sprite/twigs/twigs-2.vox b/assets/voxygen/voxel/sprite/twigs/twigs-2.vox new file mode 100644 index 0000000000..5647afff1f --- /dev/null +++ b/assets/voxygen/voxel/sprite/twigs/twigs-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77a7ead81babcbbd6c5954df0692a7e13e018a250f379f42a755914546ad189c +size 1184 diff --git a/assets/voxygen/voxel/weapon/hammer/craftsman.vox b/assets/voxygen/voxel/weapon/hammer/craftsman.vox new file mode 100644 index 0000000000..7501e83d4f --- /dev/null +++ b/assets/voxygen/voxel/weapon/hammer/craftsman.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4dccf2543585a5d0a18dea671d9b25347715a89ccab44e793faf735a0d52e62 +size 2240 diff --git a/client/src/lib.rs b/client/src/lib.rs index 7594db654b..8138288872 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,5 +1,5 @@ #![deny(unsafe_code)] -#![feature(label_break_value)] +#![feature(label_break_value, option_zip)] pub mod cmd; pub mod error; @@ -25,6 +25,7 @@ use common::{ PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG, }, + recipe::RecipeBook, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, terrain::{block::Block, TerrainChunk, TerrainChunkSize}, @@ -33,9 +34,11 @@ use common::{ use futures_executor::block_on; use futures_timer::Delay; use futures_util::{select, FutureExt}; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use image::DynamicImage; -use network::{Address, Network, Participant, Pid, Stream, PROMISES_CONSISTENCY, PROMISES_ORDERED}; +use network::{ + Network, Participant, Pid, ProtocolAddr, Stream, PROMISES_CONSISTENCY, PROMISES_ORDERED, +}; use num::traits::FloatConst; use rayon::prelude::*; use std::{ @@ -44,7 +47,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use tracing::{debug, error, warn}; +use tracing::{debug, error, trace, warn}; use uvth::{ThreadPool, ThreadPoolBuilder}; use vek::*; // TODO: remove world dependencies. We should see if we @@ -99,9 +102,11 @@ pub struct Client { pub player_list: HashMap, pub character_list: CharacterList, pub active_character_id: Option, + recipe_book: RecipeBook, + available_recipes: HashSet, _network: Network, - _participant: Arc, + participant: Option, singleton_stream: Stream, last_server_ping: f64, @@ -140,15 +145,15 @@ impl Client { // We reduce the thread count by 1 to keep rendering smooth thread_pool.set_num_threads((num_cpus::get() - 1).max(1)); - let (network, scheduler) = Network::new(Pid::new(), None); + let (network, scheduler) = Network::new(Pid::new()); thread_pool.execute(scheduler); - let participant = block_on(network.connect(Address::Tcp(addr.into())))?; + let participant = block_on(network.connect(ProtocolAddr::Tcp(addr.into())))?; let mut stream = block_on(participant.open(10, PROMISES_ORDERED | PROMISES_CONSISTENCY))?; // Wait for initial sync - let (state, entity, server_info, lod_base, lod_alt, lod_horizon, world_map) = block_on( - async { + let (state, entity, server_info, lod_base, lod_alt, lod_horizon, world_map, recipe_book) = + block_on(async { loop { match stream.recv().await? { ServerMsg::InitialSync { @@ -156,6 +161,7 @@ impl Client { server_info, time_of_day, world_map, + recipe_book, } => { // TODO: Display that versions don't match in Voxygen if &server_info.git_hash != *common::util::GIT_HASH { @@ -334,6 +340,7 @@ impl Client { lod_alt, lod_horizon, (world_map, map_size, map_bounds), + recipe_book, )); }, ServerMsg::TooManyPlayers => break Err(Error::TooManyPlayers), @@ -342,8 +349,7 @@ impl Client { }, } } - }, - )?; + })?; stream.send(ClientMsg::Ping)?; @@ -364,9 +370,11 @@ impl Client { player_list: HashMap::new(), character_list: CharacterList::default(), active_character_id: None, + recipe_book, + available_recipes: HashSet::default(), _network: network, - _participant: participant, + participant: Some(participant), singleton_stream: stream, last_server_ping: 0.0, @@ -478,10 +486,11 @@ impl Client { /// Send disconnect message to the server pub fn request_logout(&mut self) { + debug!("Requesting logout from server"); if let Err(e) = self.singleton_stream.send(ClientMsg::Disconnect) { error!( ?e, - "couldn't send disconnect package to server, did server close already?" + "Couldn't send disconnect package to server, did server close already?" ); } } @@ -535,6 +544,40 @@ impl Client { } } + pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book } + + pub fn available_recipes(&self) -> &HashSet { &self.available_recipes } + + pub fn can_craft_recipe(&self, recipe: &str) -> bool { + self.recipe_book + .get(recipe) + .zip(self.inventories().get(self.entity)) + .map(|(recipe, inv)| inv.contains_ingredients(&*recipe).is_ok()) + .unwrap_or(false) + } + + pub fn craft_recipe(&mut self, recipe: &str) -> bool { + if self.can_craft_recipe(recipe) { + self.singleton_stream + .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::CraftRecipe(recipe.to_string()), + ))) + .unwrap(); + true + } else { + false + } + } + + fn update_available_recipes(&mut self) { + self.available_recipes = self + .recipe_book + .iter() + .map(|(name, _)| name.clone()) + .filter(|name| self.can_craft_recipe(name)) + .collect(); + } + pub fn toggle_lantern(&mut self) { self.singleton_stream .send(ClientMsg::ControlEvent(ControlEvent::ToggleLantern)) @@ -776,8 +819,7 @@ impl Client { ); } self.singleton_stream - .send(ClientMsg::ControllerInputs(inputs)) - .unwrap(); + .send(ClientMsg::ControllerInputs(inputs))?; } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -1113,6 +1155,8 @@ impl Client { }, } + self.update_available_recipes(); + frontend_events.push(Event::InventoryUpdated(event)); }, ServerMsg::TerrainChunkUpdate { key, chunk } => { @@ -1357,12 +1401,16 @@ impl Client { impl Drop for Client { fn drop(&mut self) { + trace!("Dropping client"); if let Err(e) = self.singleton_stream.send(ClientMsg::Disconnect) { warn!( - "error during drop of client, couldn't send disconnect package, is the connection \ - already closed? : {}", - e + ?e, + "Error during drop of client, couldn't send disconnect package, is the connection \ + already closed?", ); } + if let Err(e) = block_on(self.participant.take().unwrap().disconnect()) { + warn!(?e, "error when disconnecting, couldn't send all data"); + } } } diff --git a/common/Cargo.toml b/common/Cargo.toml index eda6f2efcd..47dd092a67 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" no-assets = [] [dependencies] +arraygen = "0.1.13" specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" } specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" } diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index e3737728b6..f23ba05632 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -80,13 +80,14 @@ pub fn load_map A>( specifier: &str, f: F, ) -> Result, Error> { - let mut assets_write = ASSETS.write().unwrap(); + let assets_write = ASSETS.read().unwrap(); match assets_write.get(specifier) { Some(asset) => Ok(Arc::clone(asset).downcast()?), None => { + drop(assets_write); // Drop the asset hashmap to permit recursive loading let asset = Arc::new(f(A::parse(load_file(specifier, A::ENDINGS)?)?)); let clone = Arc::clone(&asset); - assets_write.insert(specifier.to_owned(), clone); + ASSETS.write().unwrap().insert(specifier.to_owned(), clone); Ok(asset) }, } diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index b09b7bfaf8..56a1ad3eda 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1,11 +1,13 @@ use crate::{ comp::{ - ability::Stage, item::Item, Body, CharacterState, EnergySource, Gravity, LightEmitter, - Projectile, StateUpdate, + ability::Stage, + item::{armor::Protection, Item, ItemKind}, + Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate, }, states::{triple_strike::*, *}, sys::character_behavior::JoinData, }; +use arraygen::Arraygen; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -16,6 +18,7 @@ pub enum CharacterAbilityType { BasicMelee, BasicRanged, Boost, + ChargedRanged, DashMelee, BasicBlock, TripleStrike(Stage), @@ -34,6 +37,7 @@ impl From<&CharacterState> for CharacterAbilityType { CharacterState::LeapMelee(_) => Self::LeapMelee, CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage), CharacterState::SpinMelee(_) => Self::SpinMelee, + CharacterState::ChargedRanged(_) => Self::ChargedRanged, _ => Self::BasicMelee, } } @@ -88,6 +92,19 @@ pub enum CharacterAbility { recover_duration: Duration, base_damage: u32, }, + ChargedRanged { + energy_cost: u32, + energy_drain: u32, + initial_damage: u32, + max_damage: u32, + initial_knockback: f32, + max_knockback: f32, + prepare_duration: Duration, + charge_duration: Duration, + recover_duration: Duration, + projectile_body: Body, + projectile_light: Option, + }, } impl CharacterAbility { @@ -129,6 +146,10 @@ impl CharacterAbility { .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), + CharacterAbility::ChargedRanged { energy_cost, .. } => update + .energy + .try_change_by(-(*energy_cost as i32), EnergySource::Ability) + .is_ok(), _ => true, } } @@ -144,25 +165,63 @@ pub struct ItemConfig { pub dodge_ability: Option, } -#[derive(Clone, PartialEq, Default, Debug, Serialize, Deserialize)] +#[derive(Arraygen, Clone, PartialEq, Default, Debug, Serialize, Deserialize)] +#[gen_array(pub fn get_armor: &Option)] pub struct Loadout { pub active_item: Option, pub second_item: Option, - pub shoulder: Option, - pub chest: Option, - pub belt: Option, - pub hand: Option, - pub pants: Option, - pub foot: Option, - pub back: Option, - pub ring: Option, - pub neck: Option, pub lantern: Option, + + #[in_array(get_armor)] + pub shoulder: Option, + #[in_array(get_armor)] + pub chest: Option, + #[in_array(get_armor)] + pub belt: Option, + #[in_array(get_armor)] + pub hand: Option, + #[in_array(get_armor)] + pub pants: Option, + #[in_array(get_armor)] + pub foot: Option, + #[in_array(get_armor)] + pub back: Option, + #[in_array(get_armor)] + pub ring: Option, + #[in_array(get_armor)] + pub neck: Option, + #[in_array(get_armor)] pub head: Option, + #[in_array(get_armor)] pub tabard: Option, } +impl Loadout { + pub fn get_damage_reduction(&self) -> f32 { + let protection = self + .get_armor() + .iter() + .flat_map(|armor| armor.as_ref()) + .filter_map(|item| { + if let ItemKind::Armor(armor) = item.kind { + Some(armor.get_protection()) + } else { + None + } + }) + .map(|protection| match protection { + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, + }) + .sum::>(); + match protection { + Some(dr) => dr / (60.0 + dr.abs()), + None => 1.0, + } + } +} + impl From<&CharacterAbility> for CharacterState { fn from(ability: &CharacterAbility) -> Self { match ability { @@ -219,7 +278,7 @@ impl From<&CharacterAbility> for CharacterState { }), CharacterAbility::BasicBlock => CharacterState::BasicBlock, CharacterAbility::Roll => CharacterState::Roll(roll::Data { - remaining_duration: Duration::from_millis(700), + remaining_duration: Duration::from_millis(500), was_wielded: false, // false by default. utils might set it to true }), CharacterAbility::TripleStrike { @@ -271,6 +330,32 @@ impl From<&CharacterAbility> for CharacterState { * this value can be removed if ability moved to * skillbar */ }), + CharacterAbility::ChargedRanged { + energy_cost: _, + energy_drain, + initial_damage, + max_damage, + initial_knockback, + max_knockback, + prepare_duration, + charge_duration, + recover_duration, + projectile_body, + projectile_light, + } => CharacterState::ChargedRanged(charged_ranged::Data { + exhausted: false, + energy_drain: *energy_drain, + initial_damage: *initial_damage, + max_damage: *max_damage, + initial_knockback: *initial_knockback, + max_knockback: *max_knockback, + prepare_duration: *prepare_duration, + charge_duration: *charge_duration, + charge_timer: Duration::default(), + recover_duration: *recover_duration, + projectile_body: *projectile_body, + projectile_light: *projectile_light, + }), } } } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 6fdcb0e3a7..be77bb5a01 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -231,6 +231,114 @@ impl Body { // Note: currently assumes sphericality pub fn height(&self) -> f32 { self.radius() * 2.0 } + + pub fn base_health(&self) -> u32 { + match self { + Body::Humanoid(_) => 52, + Body::QuadrupedSmall(_) => 44, + Body::QuadrupedMedium(_) => 72, + Body::BirdMedium(_) => 36, + Body::FishMedium(_) => 32, + Body::Dragon(_) => 256, + Body::BirdSmall(_) => 24, + Body::FishSmall(_) => 20, + Body::BipedLarge(_) => 144, + Body::Object(_) => 100, + Body::Golem(_) => 168, + Body::Critter(_) => 32, + Body::QuadrupedLow(_) => 64, + } + } + + pub fn base_health_increase(&self) -> u32 { + match self { + Body::Humanoid(_) => 5, + Body::QuadrupedSmall(_) => 4, + Body::QuadrupedMedium(_) => 7, + Body::BirdMedium(_) => 4, + Body::FishMedium(_) => 3, + Body::Dragon(_) => 26, + Body::BirdSmall(_) => 2, + Body::FishSmall(_) => 2, + Body::BipedLarge(_) => 14, + Body::Object(_) => 0, + Body::Golem(_) => 17, + Body::Critter(_) => 3, + Body::QuadrupedLow(_) => 6, + } + } + + pub fn base_exp(&self) -> u32 { + match self { + Body::Humanoid(_) => 15, + Body::QuadrupedSmall(_) => 12, + Body::QuadrupedMedium(_) => 28, + Body::BirdMedium(_) => 10, + Body::FishMedium(_) => 8, + Body::Dragon(_) => 160, + Body::BirdSmall(_) => 5, + Body::FishSmall(_) => 4, + Body::BipedLarge(_) => 75, + Body::Object(_) => 0, + Body::Golem(_) => 75, + Body::Critter(_) => 8, + Body::QuadrupedLow(_) => 24, + } + } + + pub fn base_exp_increase(&self) -> u32 { + match self { + Body::Humanoid(_) => 3, + Body::QuadrupedSmall(_) => 2, + Body::QuadrupedMedium(_) => 6, + Body::BirdMedium(_) => 2, + Body::FishMedium(_) => 2, + Body::Dragon(_) => 32, + Body::BirdSmall(_) => 1, + Body::FishSmall(_) => 1, + Body::BipedLarge(_) => 15, + Body::Object(_) => 0, + Body::Golem(_) => 15, + Body::Critter(_) => 2, + Body::QuadrupedLow(_) => 5, + } + } + + pub fn base_dmg(&self) -> u32 { + match self { + Body::Humanoid(_) => 6, + Body::QuadrupedSmall(_) => 8, + Body::QuadrupedMedium(_) => 12, + Body::BirdMedium(_) => 7, + Body::FishMedium(_) => 6, + Body::Dragon(_) => 90, + Body::BirdSmall(_) => 5, + Body::FishSmall(_) => 3, + Body::BipedLarge(_) => 36, + Body::Object(_) => 0, + Body::Golem(_) => 36, + Body::Critter(_) => 7, + Body::QuadrupedLow(_) => 11, + } + } + + pub fn base_range(&self) -> f32 { + match self { + Body::Humanoid(_) => 5.0, + Body::QuadrupedSmall(_) => 4.5, + Body::QuadrupedMedium(_) => 5.5, + Body::BirdMedium(_) => 3.5, + Body::FishMedium(_) => 3.5, + Body::Dragon(_) => 12.5, + Body::BirdSmall(_) => 3.0, + Body::FishSmall(_) => 3.0, + Body::BipedLarge(_) => 10.0, + Body::Object(_) => 3.0, + Body::Golem(_) => 7.5, + Body::Critter(_) => 3.0, + Body::QuadrupedLow(_) => 4.5, + } + } } impl Component for Body { diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index ee132711fd..a77263578d 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -66,6 +66,8 @@ pub enum CharacterState { LeapMelee(leap_melee::Data), /// Spin around, dealing damage to enemies surrounding you SpinMelee(spin_melee::Data), + /// A charged ranged attack (e.g. bow) + ChargedRanged(charged_ranged::Data), } impl CharacterState { @@ -78,7 +80,8 @@ impl CharacterState { | CharacterState::TripleStrike(_) | CharacterState::BasicBlock | CharacterState::LeapMelee(_) - | CharacterState::SpinMelee(_) => true, + | CharacterState::SpinMelee(_) + | CharacterState::ChargedRanged(_) => true, _ => false, } } @@ -90,7 +93,8 @@ impl CharacterState { | CharacterState::DashMelee(_) | CharacterState::TripleStrike(_) | CharacterState::LeapMelee(_) - | CharacterState::SpinMelee(_) => true, + | CharacterState::SpinMelee(_) + | CharacterState::ChargedRanged(_) => true, _ => false, } } @@ -102,7 +106,8 @@ impl CharacterState { | CharacterState::DashMelee(_) | CharacterState::TripleStrike(_) | CharacterState::BasicBlock - | CharacterState::LeapMelee(_) => true, + | CharacterState::LeapMelee(_) + | CharacterState::ChargedRanged(_) => true, _ => false, } } diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index ca8bcda1b0..01942d68f1 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -15,6 +15,7 @@ pub enum InventoryManip { Use(Slot), Swap(Slot, Slot), Drop(Slot), + CraftRecipe(String), } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/damage.rs b/common/src/comp/damage.rs new file mode 100644 index 0000000000..e2e6449975 --- /dev/null +++ b/common/src/comp/damage.rs @@ -0,0 +1,77 @@ +use crate::comp::Loadout; +use serde::{Deserialize, Serialize}; + +pub const BLOCK_EFFICIENCY: f32 = 0.9; + +pub struct Damage { + pub healthchange: f32, + pub source: DamageSource, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum DamageSource { + Melee, + Healing, + Projectile, + Explosion, + Falling, +} + +impl Damage { + pub fn modify_damage(&mut self, block: bool, loadout: &Loadout) { + match self.source { + DamageSource::Melee => { + // Critical hit + if rand::random() { + self.healthchange *= 1.2; + } + // Block + if block { + self.healthchange *= 1.0 - BLOCK_EFFICIENCY + } + // Armor + self.healthchange *= 1.0 - loadout.get_damage_reduction(); + + // Min damage + if self.healthchange > -1.0 { + self.healthchange = -1.0; + } + }, + DamageSource::Projectile => { + // Critical hit + if rand::random() { + self.healthchange *= 1.2; + } + // Block + if block { + self.healthchange *= 1.0 - BLOCK_EFFICIENCY + } + // Armor + self.healthchange *= 1.0 - loadout.get_damage_reduction(); + + // Min damage + if self.healthchange > -1.0 { + self.healthchange = -1.0; + } + }, + DamageSource::Explosion => { + // Critical hit + if rand::random() { + self.healthchange *= 1.2; + } + // Block + if block { + self.healthchange *= 1.0 - BLOCK_EFFICIENCY + } + // Armor + self.healthchange *= 1.0 - loadout.get_damage_reduction(); + + // Min damage + if self.healthchange > -1.0 { + self.healthchange = -1.0; + } + }, + _ => {}, + } + } +} diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 3715c88b0b..0b8f5ff3fc 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -306,8 +306,9 @@ pub const ALL_SHOULDERS: [Shoulder; 24] = [ pub enum Back { Short0 = 1, Admin = 2, + DungPurp0 = 3, } -pub const ALL_BACKS: [Back; 2] = [Back::Short0, Back::Admin]; +pub const ALL_BACKS: [Back; 3] = [Back::Short0, Back::Admin, Back::DungPurp0]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] pub enum Ring { @@ -335,7 +336,7 @@ pub enum Tabard { pub const ALL_TABARDS: [Tabard; 1] = [Tabard::Admin]; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Armor { +pub enum ArmorKind { Shoulder(Shoulder), Chest(Chest), Belt(Belt), @@ -349,5 +350,32 @@ pub enum Armor { Tabard(Tabard), } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct Stats(pub u32); +impl Armor { + /// Determines whether two pieces of armour are superficially equivalent to + /// one another (i.e: one may be substituted for the other in crafting + /// recipes or item possession checks). + pub fn superficially_eq(&self, other: &Self) -> bool { + std::mem::discriminant(&self.kind) == std::mem::discriminant(&other.kind) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct Stats { + protection: Protection, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum Protection { + Invincible, + Normal(f32), +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct Armor { + pub kind: ArmorKind, + pub stats: Stats, +} + +impl Armor { + pub fn get_protection(&self) -> Protection { self.stats.protection } +} diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 8ebb2ba2b7..895f07bd34 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -26,7 +26,12 @@ pub enum Consumable { Velorite, VeloriteFrag, PotionMinor, + PotionMed, + PotionLarge, PotionExp, + AppleShroomCurry, + AppleStick, + MushroomStick, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -44,6 +49,13 @@ pub enum Utility { pub enum Ingredient { Flower, Grass, + EmptyVial, + LeatherScraps, + ShinyGem, + Stones, + Twigs, + MortarPestle, + CraftsmanHammer, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -78,15 +90,12 @@ impl Lantern { fn default_amount() -> u32 { 1 } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ItemKind { /// Something wieldable Tool(tool::Tool), Lantern(Lantern), - Armor { - kind: armor::Armor, - stats: armor::Stats, - }, + Armor(armor::Armor), Consumable { kind: Consumable, effect: Effect, @@ -110,7 +119,7 @@ pub enum ItemKind { }, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Item { name: String, description: String, @@ -159,13 +168,25 @@ impl Item { pub fn description(&self) -> &str { &self.description } + pub fn amount(&self) -> u32 { + match &self.kind { + ItemKind::Tool(_) => 1, + ItemKind::Lantern(_) => 1, + ItemKind::Armor { .. } => 1, + ItemKind::Consumable { amount, .. } => *amount, + ItemKind::Throwable { amount, .. } => *amount, + ItemKind::Utility { amount, .. } => *amount, + ItemKind::Ingredient { amount, .. } => *amount, + } + } + pub fn try_reclaim_from_block(block: Block) -> Option { match block.kind() { - BlockKind::Apple => Some(assets::load_expect_cloned("common.items.apple")), - BlockKind::Mushroom => Some(assets::load_expect_cloned("common.items.mushroom")), - BlockKind::Velorite => Some(assets::load_expect_cloned("common.items.velorite")), + BlockKind::Apple => Some(assets::load_expect_cloned("common.items.food.apple")), + BlockKind::Mushroom => Some(assets::load_expect_cloned("common.items.food.mushroom")), + BlockKind::Velorite => Some(assets::load_expect_cloned("common.items.ore.velorite")), BlockKind::VeloriteFrag => { - Some(assets::load_expect_cloned("common.items.veloritefrag")) + Some(assets::load_expect_cloned("common.items.ore.veloritefrag")) }, BlockKind::BlueFlower => Some(assets::load_expect_cloned("common.items.flowers.blue")), BlockKind::PinkFlower => Some(assets::load_expect_cloned("common.items.flowers.pink")), @@ -185,23 +206,49 @@ impl Item { Some(assets::load_expect_cloned("common.items.grasses.medium")) }, BlockKind::ShortGrass => Some(assets::load_expect_cloned("common.items.grasses.short")), - BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.coconut")), + BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.food.coconut")), BlockKind::Chest => { let chosen = assets::load_expect::>("common.loot_table"); let chosen = chosen.choose(); Some(assets::load_expect_cloned(chosen)) }, + BlockKind::Stones => Some(assets::load_expect_cloned( + "common.items.crafting_ing.stones", + )), + BlockKind::Twigs => Some(assets::load_expect_cloned( + "common.items.crafting_ing.twigs", + )), + BlockKind::ShinyGem => Some(assets::load_expect_cloned( + "common.items.crafting_ing.shiny_gem", + )), _ => None, } } + + /// Determines whether two items are superficially equivalent to one another + /// (i.e: one may be substituted for the other in crafting recipes or + /// item possession checks). + pub fn superficially_eq(&self, other: &Self) -> bool { + match (&self.kind, &other.kind) { + (ItemKind::Tool(a), ItemKind::Tool(b)) => a.superficially_eq(b), + // TODO: Differentiate between lantern colors? + (ItemKind::Lantern(_), ItemKind::Lantern(_)) => true, + (ItemKind::Armor(a), ItemKind::Armor(b)) => a.superficially_eq(b), + (ItemKind::Consumable { kind: a, .. }, ItemKind::Consumable { kind: b, .. }) => a == b, + (ItemKind::Throwable { kind: a, .. }, ItemKind::Throwable { kind: b, .. }) => a == b, + (ItemKind::Utility { kind: a, .. }, ItemKind::Utility { kind: b, .. }) => a == b, + (ItemKind::Ingredient { kind: a, .. }, ItemKind::Ingredient { kind: b, .. }) => a == b, + _ => false, + } + } } impl Component for Item { type Storage = FlaggedStorage>; } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ItemDrop(pub Item); impl Component for ItemDrop { diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index abeb4a04c9..f5dcefb4fd 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,8 +2,7 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::comp::{ - body::object, projectile, Body, CharacterAbility, Gravity, HealthChange, HealthSource, - LightEmitter, Projectile, + body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -262,11 +261,7 @@ impl Tool { projectile: Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damage(HealthChange { - // TODO: This should not be fixed (?) - amount: -3, - cause: HealthSource::Projectile { owner: None }, - }), + projectile::Effect::Damage(-3), projectile::Effect::Knockback(10.0), projectile::Effect::RewardEnergy(100), projectile::Effect::Vanish, @@ -278,29 +273,18 @@ impl Tool { projectile_light: None, projectile_gravity: Some(Gravity(0.2)), }, - BasicRanged { - energy_cost: 350, - holdable: true, - prepare_duration: Duration::from_millis(250), - recover_duration: Duration::from_millis(700), - projectile: Projectile { - hit_solid: vec![projectile::Effect::Stick], - hit_entity: vec![ - projectile::Effect::Damage(HealthChange { - // TODO: This should not be fixed (?) - amount: -9, - cause: HealthSource::Projectile { owner: None }, - }), - projectile::Effect::Knockback(15.0), - projectile::Effect::RewardEnergy(50), - projectile::Effect::Vanish, - ], - time_left: Duration::from_secs(15), - owner: None, - }, + ChargedRanged { + energy_cost: 0, + energy_drain: 300, + initial_damage: 3, + max_damage: 15, + initial_knockback: 10.0, + max_knockback: 20.0, + prepare_duration: Duration::from_millis(100), + charge_duration: Duration::from_millis(1500), + recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - projectile_gravity: Some(Gravity(0.05)), }, ], Dagger(_) => vec![ @@ -336,11 +320,7 @@ impl Tool { projectile: Projectile { hit_solid: vec![projectile::Effect::Vanish], hit_entity: vec![ - projectile::Effect::Damage(HealthChange { - // TODO: This should not be fixed (?) - amount: -3, - cause: HealthSource::Projectile { owner: None }, - }), + projectile::Effect::Damage(-3), projectile::Effect::RewardEnergy(150), projectile::Effect::Vanish, ], @@ -453,4 +433,11 @@ impl Tool { }], } } + + /// Determines whether two tools are superficially equivalent to one another + /// (i.e: one may be substituted for the other in crafting recipes or + /// item possession checks). + pub fn superficially_eq(&self, other: &Self) -> bool { + ToolCategory::from(self.kind) == ToolCategory::from(other.kind) + } } diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 657386fec5..5516704514 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,7 +1,7 @@ pub mod item; pub mod slot; -use crate::assets; +use crate::{assets, recipe::Recipe}; use item::{Consumable, Item, ItemKind}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage, HashMapStorage}; @@ -11,7 +11,7 @@ use std::ops::Not; // The limit on distance between the entity and a collectible (squared) pub const MAX_PICKUP_RANGE_SQR: f32 = 64.0; -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Inventory { pub slots: Vec>, pub amount: u32, @@ -185,14 +185,11 @@ impl Inventory { /// Add a series of items to inventory, returning any which do not fit as an /// error. - pub fn push_all>(&mut self, mut items: I) -> Result<(), Error> { + pub fn push_all>(&mut self, items: I) -> Result<(), Error> { // Vec doesn't allocate for zero elements so this should be cheap let mut leftovers = Vec::new(); - let mut slots = self.slots.iter_mut(); - for item in &mut items { - if let Some(slot) = slots.find(|slot| slot.is_none()) { - slot.replace(item); - } else { + for item in items { + if let Some(item) = self.push(item) { leftovers.push(item); } } @@ -241,6 +238,132 @@ impl Inventory { } } + /// Checks if inserting item exists in given cell. Inserts an item if it + /// exists. + pub fn insert_or_stack(&mut self, cell: usize, item: Item) -> Result, Item> { + match &item.kind { + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => { + self.insert(cell, item) + }, + ItemKind::Utility { + amount: new_amount, .. + } => match self.slots.get_mut(cell) { + Some(Some(slot_item)) => { + if slot_item.name() == item.name() + && slot_item.description() == item.description() + { + if let Item { + kind: ItemKind::Utility { amount, .. }, + .. + } = slot_item + { + *amount += *new_amount; + self.recount_items(); + Ok(None) + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + }, + Some(None) => self.insert(cell, item), + None => Err(item), + }, + ItemKind::Ingredient { + amount: new_amount, .. + } => match self.slots.get_mut(cell) { + Some(Some(slot_item)) => { + if slot_item.name() == item.name() + && slot_item.description() == item.description() + { + if let Item { + kind: ItemKind::Ingredient { amount, .. }, + .. + } = slot_item + { + *amount += *new_amount; + self.recount_items(); + Ok(None) + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + }, + Some(None) => self.insert(cell, item), + None => Err(item), + }, + ItemKind::Consumable { + amount: new_amount, .. + } => match self.slots.get_mut(cell) { + Some(Some(slot_item)) => { + if slot_item.name() == item.name() + && slot_item.description() == item.description() + { + if let Item { + kind: ItemKind::Consumable { amount, .. }, + .. + } = slot_item + { + *amount += *new_amount; + self.recount_items(); + Ok(None) + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + }, + Some(None) => self.insert(cell, item), + None => Err(item), + }, + ItemKind::Throwable { + amount: new_amount, .. + } => match self.slots.get_mut(cell) { + Some(Some(slot_item)) => { + if slot_item.name() == item.name() + && slot_item.description() == item.description() + { + if let Item { + kind: ItemKind::Throwable { amount, .. }, + .. + } = slot_item + { + *amount += *new_amount; + self.recount_items(); + Ok(None) + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + } else { + let old_item = std::mem::replace(slot_item, item); + self.recount_items(); + Ok(Some(old_item)) + } + }, + Some(None) => self.insert(cell, item), + None => Err(item), + }, + } + } + pub fn is_full(&self) -> bool { self.slots.iter().all(|slot| slot.is_some()) } /// O(n) count the number of items in this inventory. @@ -340,6 +463,52 @@ impl Inventory { None } } + + /// Determine how many of a particular item there is in the inventory. + pub fn item_count(&self, item: &Item) -> usize { + self.slots() + .iter() + .flatten() + .filter(|it| it.superficially_eq(item)) + .map(|it| it.amount() as usize) + .sum() + } + + /// Determine whether the inventory contains the ingredients for a recipe. + /// If it does, return a vector of numbers, where is number corresponds + /// to an inventory slot, along with the number of items that need + /// removing from it. It items are missing, return the missing items, and + /// how many are missing. + pub fn contains_ingredients<'a>( + &self, + recipe: &'a Recipe, + ) -> Result, Vec<(&'a Item, usize)>> { + let mut slot_claims = vec![0; self.slots.len()]; + let mut missing = Vec::new(); + + for (input, mut needed) in recipe.inputs() { + let mut contains_any = false; + + for (i, slot) in self.slots().iter().enumerate() { + if let Some(item) = slot.as_ref().filter(|item| item.superficially_eq(input)) { + let can_claim = (item.amount() as usize - slot_claims[i]).min(needed); + slot_claims[i] += can_claim; + needed -= can_claim; + contains_any = true; + } + } + + if needed > 0 || !contains_any { + missing.push((input, needed)); + } + } + + if missing.len() == 0 { + Ok(slot_claims) + } else { + Err(missing) + } + } } impl Default for Inventory { @@ -348,8 +517,8 @@ impl Default for Inventory { slots: vec![None; 36], amount: 0, }; - inventory.push(assets::load_expect_cloned("common.items.cheese")); - inventory.push(assets::load_expect_cloned("common.items.apple")); + inventory.push(assets::load_expect_cloned("common.items.food.cheese")); + inventory.push(assets::load_expect_cloned("common.items.food.apple")); inventory } } @@ -358,7 +527,7 @@ impl Component for Inventory { type Storage = HashMapStorage; } -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum InventoryUpdateEvent { Init, Used, @@ -371,6 +540,7 @@ pub enum InventoryUpdateEvent { CollectFailed, Possession, Debug, + Craft, } impl Default for InventoryUpdateEvent { diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs index 312a81ac3c..db09354b25 100644 --- a/common/src/comp/inventory/slot.rs +++ b/common/src/comp/inventory/slot.rs @@ -1,4 +1,7 @@ -use crate::{comp, comp::item}; +use crate::{ + comp, + comp::{item, item::armor}, +}; use comp::{Inventory, Loadout}; use serde::{Deserialize, Serialize}; use tracing::warn; @@ -47,9 +50,10 @@ impl Slot { impl EquipSlot { fn can_hold(self, item_kind: &item::ItemKind) -> bool { + use armor::Armor; use item::ItemKind; match (self, item_kind) { - (Self::Armor(slot), ItemKind::Armor { kind, .. }) => slot.can_hold(kind), + (Self::Armor(slot), ItemKind::Armor(Armor { kind, .. })) => slot.can_hold(kind), (Self::Mainhand, ItemKind::Tool(_)) => true, (Self::Offhand, ItemKind::Tool(_)) => true, (Self::Lantern, ItemKind::Lantern(_)) => true, @@ -59,20 +63,20 @@ impl EquipSlot { } impl ArmorSlot { - fn can_hold(self, armor: &item::armor::Armor) -> bool { - use item::armor::Armor; + fn can_hold(self, armor: &item::armor::ArmorKind) -> bool { + use item::armor::ArmorKind; match (self, armor) { - (Self::Head, Armor::Head(_)) => true, - (Self::Neck, Armor::Neck(_)) => true, - (Self::Shoulders, Armor::Shoulder(_)) => true, - (Self::Chest, Armor::Chest(_)) => true, - (Self::Hands, Armor::Hand(_)) => true, - (Self::Ring, Armor::Ring(_)) => true, - (Self::Back, Armor::Back(_)) => true, - (Self::Belt, Armor::Belt(_)) => true, - (Self::Legs, Armor::Pants(_)) => true, - (Self::Feet, Armor::Foot(_)) => true, - (Self::Tabard, Armor::Tabard(_)) => true, + (Self::Head, ArmorKind::Head(_)) => true, + (Self::Neck, ArmorKind::Neck(_)) => true, + (Self::Shoulders, ArmorKind::Shoulder(_)) => true, + (Self::Chest, ArmorKind::Chest(_)) => true, + (Self::Hands, ArmorKind::Hand(_)) => true, + (Self::Ring, ArmorKind::Ring(_)) => true, + (Self::Back, ArmorKind::Back(_)) => true, + (Self::Belt, ArmorKind::Belt(_)) => true, + (Self::Legs, ArmorKind::Pants(_)) => true, + (Self::Feet, ArmorKind::Foot(_)) => true, + (Self::Tabard, ArmorKind::Tabard(_)) => true, _ => false, } } @@ -285,22 +289,23 @@ pub fn swap( /// assert_eq!(boots, loadout.foot); /// ``` pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) { - use item::{armor::Armor, ItemKind}; + use armor::Armor; + use item::{armor::ArmorKind, ItemKind}; let equip_slot = inventory.get(slot).and_then(|i| match &i.kind { ItemKind::Tool(_) => Some(EquipSlot::Mainhand), - ItemKind::Armor { kind, .. } => Some(EquipSlot::Armor(match kind { - Armor::Head(_) => ArmorSlot::Head, - Armor::Neck(_) => ArmorSlot::Neck, - Armor::Shoulder(_) => ArmorSlot::Shoulders, - Armor::Chest(_) => ArmorSlot::Chest, - Armor::Hand(_) => ArmorSlot::Hands, - Armor::Ring(_) => ArmorSlot::Ring, - Armor::Back(_) => ArmorSlot::Back, - Armor::Belt(_) => ArmorSlot::Belt, - Armor::Pants(_) => ArmorSlot::Legs, - Armor::Foot(_) => ArmorSlot::Feet, - Armor::Tabard(_) => ArmorSlot::Tabard, + ItemKind::Armor(Armor { kind, .. }) => Some(EquipSlot::Armor(match kind { + ArmorKind::Head(_) => ArmorSlot::Head, + ArmorKind::Neck(_) => ArmorSlot::Neck, + ArmorKind::Shoulder(_) => ArmorSlot::Shoulders, + ArmorKind::Chest(_) => ArmorSlot::Chest, + ArmorKind::Hand(_) => ArmorSlot::Hands, + ArmorKind::Ring(_) => ArmorSlot::Ring, + ArmorKind::Back(_) => ArmorSlot::Back, + ArmorKind::Belt(_) => ArmorSlot::Belt, + ArmorKind::Pants(_) => ArmorSlot::Legs, + ArmorKind::Foot(_) => ArmorSlot::Feet, + ArmorKind::Tabard(_) => ArmorSlot::Tabard, })), ItemKind::Lantern(_) => Some(EquipSlot::Lantern), _ => None, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 3efd7e9381..b99b575450 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -5,6 +5,7 @@ mod body; mod character_state; mod chat; mod controller; +mod damage; mod energy; mod inputs; mod inventory; @@ -32,6 +33,7 @@ pub use controller::{ Climb, ControlAction, ControlEvent, Controller, ControllerInputs, Input, InventoryManip, MountState, Mounting, }; +pub use damage::{Damage, DamageSource}; pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; pub use inventory::{ @@ -43,7 +45,7 @@ pub use last::Last; pub use location::{Waypoint, WaypointArea}; pub use misc::Object; pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; -pub use player::Player; +pub use player::{Player, MAX_MOUNT_RANGE_SQR}; pub use projectile::Projectile; pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet}; pub use stats::{Exp, HealthChange, HealthSource, Level, Stats}; diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 0527b20284..3c4bbe2002 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -4,6 +4,7 @@ use specs::{Component, FlaggedStorage, NullStorage}; use specs_idvs::IdvStorage; const MAX_ALIAS_LEN: usize = 32; +pub const MAX_MOUNT_RANGE_SQR: i32 = 20000; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Player { diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index d029075850..8346b7cf52 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -1,4 +1,4 @@ -use crate::{comp, sync::Uid}; +use crate::sync::Uid; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -6,7 +6,7 @@ use std::time::Duration; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Effect { - Damage(comp::HealthChange), + Damage(i32), Knockback(f32), RewardEnergy(u32), Explode { power: f32 }, @@ -25,21 +25,6 @@ pub struct Projectile { pub owner: Option, } -impl Projectile { - pub fn set_owner(&mut self, new_owner: Uid) { - self.owner = Some(new_owner); - for e in self.hit_solid.iter_mut().chain(self.hit_entity.iter_mut()) { - if let Effect::Damage(comp::HealthChange { - cause: comp::HealthSource::Projectile { owner, .. }, - .. - }) = e - { - *owner = Some(new_owner); - } - } - } -} - impl Component for Projectile { type Storage = FlaggedStorage>; } diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 98392efbd9..3e8bd44a9e 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -134,80 +134,6 @@ pub struct Stats { pub body_type: Body, } -impl Body { - pub fn base_health(&self) -> u32 { - match self { - Body::Humanoid(_) => 52, - Body::QuadrupedSmall(_) => 44, - Body::QuadrupedMedium(_) => 72, - Body::BirdMedium(_) => 36, - Body::FishMedium(_) => 32, - Body::Dragon(_) => 256, - Body::BirdSmall(_) => 24, - Body::FishSmall(_) => 20, - Body::BipedLarge(_) => 144, - Body::Object(_) => 100, - Body::Golem(_) => 168, - Body::Critter(_) => 32, - Body::QuadrupedLow(_) => 64, - } - } - - pub fn base_health_increase(&self) -> u32 { - match self { - Body::Humanoid(_) => 5, - Body::QuadrupedSmall(_) => 4, - Body::QuadrupedMedium(_) => 7, - Body::BirdMedium(_) => 4, - Body::FishMedium(_) => 3, - Body::Dragon(_) => 26, - Body::BirdSmall(_) => 2, - Body::FishSmall(_) => 2, - Body::BipedLarge(_) => 14, - Body::Object(_) => 0, - Body::Golem(_) => 17, - Body::Critter(_) => 3, - Body::QuadrupedLow(_) => 6, - } - } - - pub fn base_exp(&self) -> u32 { - match self { - Body::Humanoid(_) => 15, - Body::QuadrupedSmall(_) => 12, - Body::QuadrupedMedium(_) => 28, - Body::BirdMedium(_) => 10, - Body::FishMedium(_) => 8, - Body::Dragon(_) => 160, - Body::BirdSmall(_) => 5, - Body::FishSmall(_) => 4, - Body::BipedLarge(_) => 75, - Body::Object(_) => 0, - Body::Golem(_) => 75, - Body::Critter(_) => 8, - Body::QuadrupedLow(_) => 24, - } - } - - pub fn base_exp_increase(&self) -> u32 { - match self { - Body::Humanoid(_) => 3, - Body::QuadrupedSmall(_) => 2, - Body::QuadrupedMedium(_) => 6, - Body::BirdMedium(_) => 2, - Body::FishMedium(_) => 2, - Body::Dragon(_) => 32, - Body::BirdSmall(_) => 1, - Body::FishSmall(_) => 1, - Body::BipedLarge(_) => 15, - Body::Object(_) => 0, - Body::Golem(_) => 15, - Body::Critter(_) => 2, - Body::QuadrupedLow(_) => 5, - } - } -} - impl Stats { pub fn should_die(&self) -> bool { self.health.current == 0 } diff --git a/common/src/lib.rs b/common/src/lib.rs index c2d04628e3..fb04ff28b1 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -26,6 +26,7 @@ pub mod msg; pub mod npc; pub mod path; pub mod ray; +pub mod recipe; pub mod region; pub mod spiral; pub mod state; diff --git a/common/src/loadout_builder.rs b/common/src/loadout_builder.rs index 1ed2512b0a..b3b65bd5bd 100644 --- a/common/src/loadout_builder.rs +++ b/common/src/loadout_builder.rs @@ -24,44 +24,6 @@ use std::time::Duration; /// ``` pub struct LoadoutBuilder(Loadout); -impl Body { - pub fn base_dmg(&self) -> u32 { - match self { - Body::Humanoid(_) => 8, - Body::QuadrupedSmall(_) => 7, - Body::QuadrupedMedium(_) => 10, - Body::BirdMedium(_) => 6, - Body::FishMedium(_) => 5, - Body::Dragon(_) => 75, - Body::BirdSmall(_) => 4, - Body::FishSmall(_) => 3, - Body::BipedLarge(_) => 30, - Body::Object(_) => 0, - Body::Golem(_) => 30, - Body::Critter(_) => 6, - Body::QuadrupedLow(_) => 9, - } - } - - pub fn base_range(&self) -> f32 { - match self { - Body::Humanoid(_) => 5.0, - Body::QuadrupedSmall(_) => 4.5, - Body::QuadrupedMedium(_) => 5.5, - Body::BirdMedium(_) => 3.5, - Body::FishMedium(_) => 3.5, - Body::Dragon(_) => 12.5, - Body::BirdSmall(_) => 3.0, - Body::FishSmall(_) => 3.0, - Body::BipedLarge(_) => 10.0, - Body::Object(_) => 3.0, - Body::Golem(_) => 7.5, - Body::Critter(_) => 3.0, - Body::QuadrupedLow(_) => 4.5, - } - } -} - impl LoadoutBuilder { #[allow(clippy::new_without_default)] // TODO: Pending review in #587 pub fn new() -> Self { diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index cf878ccb37..eb0aafe534 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,7 +1,9 @@ use super::{ClientState, EcsCompPacket}; use crate::{ character::CharacterItem, - comp, state, sync, + comp, + recipe::RecipeBook, + state, sync, sync::Uid, terrain::{Block, TerrainChunk}, }; @@ -175,6 +177,7 @@ pub enum ServerMsg { server_info: ServerInfo, time_of_day: state::TimeOfDay, world_map: WorldMapMsg, + recipe_book: RecipeBook, }, /// An error occurred while loading character data CharacterDataLoadError(String), diff --git a/common/src/path.rs b/common/src/path.rs index 8388edb36c..d6c10bed98 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -4,7 +4,7 @@ use crate::{ vol::{BaseVol, ReadVol}, }; use hashbrown::hash_map::DefaultHashBuilder; -use rand::{thread_rng, Rng}; +use rand::prelude::*; use std::iter::FromIterator; use vek::*; @@ -56,6 +56,18 @@ impl From>> for Route { fn from(path: Path>) -> Self { Self { path, next_idx: 0 } } } +pub struct TraversalConfig { + /// The distance to a node at which node is considered visited. + pub node_tolerance: f32, + /// The slowdown factor when following corners. + /// 0.0 = no slowdown on corners, 1.0 = total slowdown on corners. + pub slow_factor: f32, + /// Whether the agent is currently on the ground. + pub on_ground: bool, + /// The distance to the target below which it is considered reached. + pub min_tgt_dist: f32, +} + impl Route { pub fn path(&self) -> &Path> { &self.path } @@ -70,91 +82,228 @@ impl Route { vol: &V, pos: Vec3, vel: Vec3, - traversal_tolerance: f32, + traversal_cfg: TraversalConfig, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, { - let next0 = self - .next(0) - .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); - let next1 = self.next(1).unwrap_or(next0); - if vol.get(next0).map(|b| b.is_solid()).unwrap_or(false) { - None - } else { - let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); - if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0) - && next_tgt.z - pos.z < 0.2 - && next_tgt.z - pos.z > -2.2 + let (next0, next1, next_tgt, be_precise) = loop { + let next0 = self + .next(0) + .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); + + // Stop using obstructed paths + if vol.get(next0).map(|b| b.is_solid()).unwrap_or(false) { + return None; + } + + let diagonals = [ + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(-1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, -1), + Vec2::new(0, -1), + Vec2::new(1, -1), + ]; + + let next1 = self.next(1).unwrap_or(next0); + + let be_precise = diagonals.iter().any(|pos| { + !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 0)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -1)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -2)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 1)) + }); + + let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next_tgt = next0_tgt; + + // Maybe skip a node (useful with traversing downhill) + let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) { + next0_tgt + } else { + next1_tgt + }; + + // Determine whether we're close enough to the next to to consider it completed + let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy()); + if dist_sqrd < traversal_cfg.node_tolerance.powf(2.0) * if be_precise { 0.25 } else { 1.0 } + && (pos.z - closest_tgt.z > 1.2 || (pos.z - closest_tgt.z > -0.2 && traversal_cfg.on_ground)) + && (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05)) && vel.z <= 0.0 + // Only consider the node reached if there's nothing solid between us and it && vol - .ray(pos + Vec3::unit_z() * 0.5, next_tgt + Vec3::unit_z() * 0.5) + .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) .until(|block| block.is_solid()) .cast() .0 - > pos.distance(next_tgt) * 0.9 + > pos.distance(closest_tgt) * 0.9 + && self.next_idx < self.path.len() { + // Node completed, move on to the next one self.next_idx += 1; + } else { + // The next node hasn't been reached yet, use it as a target + break (next0, next1, next_tgt, be_precise); } + }; - let line = LineSegment2 { - start: pos.xy(), - end: pos.xy() + vel.xy() * 100.0, - }; - - let align = |block_pos: Vec3| { - (0..2) - .map(|i| (0..2).map(move |j| Vec2::new(i, j))) - .flatten() - .map(|rpos| block_pos + rpos) - .map(|block_pos| { - let block_posf = block_pos.xy().map(|e| e as f32); - let proj = line.projected_point(block_posf); - let clamped = proj.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32), - ); - - (proj.distance_squared(clamped), clamped) - }) - .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) - .unwrap() - .1 - }; - - let cb = CubicBezier2 { - start: pos.xy(), - ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_else(Vec2::zero), - ctrl1: align(next0), - end: align(next1), - }; - - let tgt2d = cb.evaluate(0.5); - let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; - let tgt_dir = (tgt - pos) - .xy() - .try_normalized() - .unwrap_or_else(Vec2::unit_y); - let next_dir = cb - .evaluate_derivative(0.5) - .try_normalized() - .unwrap_or(tgt_dir); - - //let vel_dir = vel.xy().try_normalized().unwrap_or(Vec2::zero()); - //let avg_dir = (tgt_dir * 0.2 + vel_dir * - // 0.8).try_normalized().unwrap_or(Vec2::zero()); let bearing = - // Vec3::::from(avg_dir * (tgt - pos).xy().magnitude()) + Vec3::unit_z() * - // (tgt.z - pos.z); - - Some(( - tgt - pos, - next_dir - .dot(vel.xy().try_normalized().unwrap_or_else(Vec2::zero)) - .max(0.0) - * 0.75 - + 0.25, - )) + fn gradient(line: LineSegment2) -> f32 { + let r = (line.start.y - line.end.y) / (line.start.x - line.end.x); + if r.is_nan() { 100000.0 } else { r } } + + fn intersect(a: LineSegment2, b: LineSegment2) -> Option> { + let ma = gradient(a); + let mb = gradient(b); + + let ca = a.start.y - ma * a.start.x; + let cb = b.start.y - mb * b.start.x; + + if (ma - mb).abs() < 0.0001 || (ca - cb).abs() < 0.0001 { + None + } else { + let x = (cb - ca) / (ma - mb); + let y = ma * x + ca; + + Some(Vec2::new(x, y)) + } + } + + // We don't always want to aim for the centre of block since this can create + // jerky zig-zag movement. This function attempts to find a position + // inside a target block's area that aligned nicely with our velocity. + // This has a twofold benefit: + // + // 1. Entities can move at any angle when + // running on a flat surface + // + // 2. We don't have to search diagonals when + // pathfinding - cartesian positions are enough since this code will + // make the entity move smoothly along them + let corners = [ + Vec2::new(0, 0), + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(0, 0), // Repeated start + ]; + + let vel_line = LineSegment2 { + start: pos.xy(), + end: pos.xy() + vel.xy() * 100.0, + }; + + let align = |block_pos: Vec3, precision: f32| { + let lerp_block = + |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision); + + (0..4) + .filter_map(|i| { + let edge_line = LineSegment2 { + start: lerp_block( + (block_pos.xy() + corners[i]).map(|e| e as f32), + precision, + ), + end: lerp_block( + (block_pos.xy() + corners[i + 1]).map(|e| e as f32), + precision, + ), + }; + intersect(vel_line, edge_line).filter(|intersect| { + intersect + .clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32 + 1.0), + ) + .distance_squared(*intersect) + < 0.001 + }) + }) + .min_by_key(|intersect: &Vec2| { + (intersect.distance_squared(vel_line.end) * 1000.0) as i32 + }) + .unwrap_or_else(|| { + (0..2) + .map(|i| (0..2).map(move |j| Vec2::new(i, j))) + .flatten() + .map(|rpos| block_pos + rpos) + .map(|block_pos| { + let block_posf = block_pos.xy().map(|e| e as f32); + let proj = vel_line.projected_point(block_posf); + let clamped = lerp_block( + proj.clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32), + ), + precision, + ); + + (proj.distance_squared(clamped), clamped) + }) + .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) + .unwrap() + .1 + }) + }; + + let bez = CubicBezier2 { + start: pos.xy(), + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_default() * 1.0, + ctrl1: align(next0, 1.0), + end: align(next1, 1.0), + }; + + // Use a cubic spline of the next few targets to come up with a sensible target + // position. We want to use a position that gives smooth movement but is + // also accurate enough to avoid the agent getting stuck under ledges or + // falling off walls. + let next_dir = bez + .evaluate_derivative(0.85) + .try_normalized() + .unwrap_or_default(); + let straight_factor = next_dir + .dot(vel.xy().try_normalized().unwrap_or(next_dir)) + .max(0.0) + .powf(2.0); + + let bez = CubicBezier2 { + start: pos.xy(), + ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or_default() * 1.0, + ctrl1: align( + next0, + (1.0 - if (next0.z as f32 - pos.z).abs() < 0.25 && !be_precise { + straight_factor + } else { + 0.0 + }) + .max(0.1), + ), + end: align(next1, 1.0), + }; + + let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 { + 0.25 + } else { + 0.5 + }); + let tgt = if be_precise { + next_tgt + } else { + Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z + }; + + Some(( + tgt - pos, + // Control the entity's speed to hopefully stop us falling off walls on sharp corners. + // This code is very imperfect: it does its best but it can still fail for particularly + // fast entities. + straight_factor * traversal_cfg.slow_factor + (1.0 - traversal_cfg.slow_factor), + )) + .filter(|(bearing, _)| bearing.z < 2.1) } } @@ -178,41 +327,64 @@ impl Chaser { pos: Vec3, vel: Vec3, tgt: Vec3, - min_dist: f32, - traversal_tolerance: f32, + traversal_cfg: TraversalConfig, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, { let pos_to_tgt = pos.distance(tgt); - if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() < min_dist.powf(2.0) { + // If we're already close to the target then there's nothing to do + if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() + < traversal_cfg.min_tgt_dist.powf(2.0) + { + self.route = None; return None; } let bearing = if let Some(end) = self.route.as_ref().and_then(|r| r.path().end().copied()) { let end_to_tgt = end.map(|e| e as f32).distance(tgt); - if end_to_tgt > pos_to_tgt * 0.3 + 5.0 || thread_rng().gen::() < 0.005 { + // If the target has moved significantly since the path was generated then it's + // time to search for a new path. Also, do this randomly from time + // to time to avoid any edge cases that cause us to get stuck. In + // theory this shouldn't happen, but in practice the world is full + // of unpredictable obstacles that are more than willing to mess up + // our day. TODO: Come up with a better heuristic for this + if end_to_tgt > pos_to_tgt * 0.3 + 5.0 + /* || thread_rng().gen::() < 0.005 */ + { None } else { self.route .as_mut() - .and_then(|r| r.traverse(vol, pos, vel, traversal_tolerance)) + .and_then(|r| r.traverse(vol, pos, vel, traversal_cfg)) + // In theory this filter isn't needed, but in practice agents often try to take + // stale paths that start elsewhere. This code makes sure that we're only using + // paths that start near us, avoiding the agent doubling back to chase a stale + // path. + .filter(|(bearing, _)| bearing.xy() + .magnitude_squared() < 1.75f32.powf(2.0) + && thread_rng().gen::() > 0.025) } } else { None }; - // TODO: What happens when we get stuck? - if let Some(bearing) = bearing { - Some(bearing) + if let Some((bearing, speed)) = bearing { + Some((bearing, speed)) } else { + // Only search for a path if the target has moved from their last position. We + // don't want to be thrashing the pathfinding code for targets that + // we're unable to access! if self .last_search_tgt .map(|last_tgt| last_tgt.distance(tgt) > pos_to_tgt * 0.15 + 5.0) .unwrap_or(true) + || self.astar.is_some() + || self.route.is_none() { let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt); + // Don't use a stale path if start_pos.distance_squared(pos) < 4.0f32.powf(2.0) { self.route = path.map(Route::from); } else { @@ -225,6 +397,24 @@ impl Chaser { } } +#[allow(clippy::float_cmp)] // TODO: Pending review in #587 +fn walkable(vol: &V, pos: Vec3) -> bool +where + V: BaseVol + ReadVol, +{ + vol.get(pos - Vec3::new(0, 0, 1)) + .map(|b| b.is_solid() && b.get_height() == 1.0) + .unwrap_or(false) + && vol + .get(pos + Vec3::new(0, 0, 0)) + .map(|b| !b.is_solid()) + .unwrap_or(true) + && vol + .get(pos + Vec3::new(0, 0, 1)) + .map(|b| !b.is_solid()) + .unwrap_or(true) +} + #[allow(clippy::float_cmp)] // TODO: Pending review in #587 fn find_path( astar: &mut Option, DefaultHashBuilder>>, @@ -235,19 +425,7 @@ fn find_path( where V: BaseVol + ReadVol, { - let is_walkable = |pos: &Vec3| { - vol.get(*pos - Vec3::new(0, 0, 1)) - .map(|b| b.is_solid() && b.get_height() == 1.0) - .unwrap_or(false) - && vol - .get(*pos + Vec3::new(0, 0, 0)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - && vol - .get(*pos + Vec3::new(0, 0, 1)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - }; + let is_walkable = |pos: &Vec3| walkable(vol, *pos); let get_walkable_z = |pos| { let mut z_incr = 0; for _ in 0..32 { @@ -291,23 +469,23 @@ where Vec3::new(0, 0, -1), // Downwards ]; - let walkable = [ - is_walkable(&(pos + Vec3::new(1, 0, 0))), - is_walkable(&(pos + Vec3::new(-1, 0, 0))), - is_walkable(&(pos + Vec3::new(0, 1, 0))), - is_walkable(&(pos + Vec3::new(0, -1, 0))), - ]; + // let walkable = [ + // is_walkable(&(pos + Vec3::new(1, 0, 0))), + // is_walkable(&(pos + Vec3::new(-1, 0, 0))), + // is_walkable(&(pos + Vec3::new(0, 1, 0))), + // is_walkable(&(pos + Vec3::new(0, -1, 0))), + // ]; - const DIAGONALS: [(Vec3, [usize; 2]); 8] = [ - (Vec3::new(1, 1, 0), [0, 2]), - (Vec3::new(-1, 1, 0), [1, 2]), - (Vec3::new(1, -1, 0), [0, 3]), - (Vec3::new(-1, -1, 0), [1, 3]), - (Vec3::new(1, 1, 1), [0, 2]), - (Vec3::new(-1, 1, 1), [1, 2]), - (Vec3::new(1, -1, 1), [0, 3]), - (Vec3::new(-1, -1, 1), [1, 3]), - ]; + // const DIAGONALS: [(Vec3, [usize; 2]); 8] = [ + // (Vec3::new(1, 1, 0), [0, 2]), + // (Vec3::new(-1, 1, 0), [1, 2]), + // (Vec3::new(1, -1, 0), [0, 3]), + // (Vec3::new(-1, -1, 0), [1, 3]), + // (Vec3::new(1, 1, 1), [0, 2]), + // (Vec3::new(-1, 1, 1), [1, 2]), + // (Vec3::new(1, -1, 1), [0, 3]), + // (Vec3::new(-1, -1, 1), [1, 3]), + // ]; DIRS.iter() .map(move |dir| (pos, dir)) @@ -331,24 +509,26 @@ where .unwrap_or(true))) }) .map(move |(pos, dir)| pos + dir) - .chain( - DIAGONALS - .iter() - .filter(move |(dir, [a, b])| { - is_walkable(&(pos + *dir)) && walkable[*a] && walkable[*b] - }) - .map(move |(dir, _)| pos + *dir), - ) - }; - - let crow_line = LineSegment2 { - start: startf.xy(), - end: endf.xy(), + // .chain( + // DIAGONALS + // .iter() + // .filter(move |(dir, [a, b])| { + // is_walkable(&(pos + *dir)) && walkable[*a] && + // walkable[*b] }) + // .map(move |(dir, _)| pos + *dir), + // ) }; let transition = |a: &Vec3, b: &Vec3| { + let crow_line = LineSegment2 { + start: startf.xy(), + end: endf.xy(), + }; + + // Modify the heuristic a little in order to prefer paths that take us on a + // straight line toward our target. This means we get smoother movement. 1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025 - + (b.z - a.z - 1).max(0) as f32 * 3.0 + + (b.z - a.z - 1).max(0) as f32 * 10.0 }; let satisfied = |pos: &Vec3| pos == &end; diff --git a/common/src/recipe.rs b/common/src/recipe.rs new file mode 100644 index 0000000000..bad5a81abf --- /dev/null +++ b/common/src/recipe.rs @@ -0,0 +1,99 @@ +use crate::{ + assets::{self, Asset}, + comp::{Inventory, Item}, +}; +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; +use std::{fs::File, io::BufReader, sync::Arc}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Recipe { + pub output: (Item, usize), + pub inputs: Vec<(Item, usize)>, +} + +#[allow(clippy::type_complexity)] +impl Recipe { + /// Perform a recipe, returning a list of missing items on failure + pub fn perform( + &self, + inv: &mut Inventory, + ) -> Result, Vec<(&Item, usize)>> { + // Get ingredient cells from inventory, + inv.contains_ingredients(self)? + .into_iter() + .enumerate() + .for_each(|(i, n)| { + (0..n).for_each(|_| { + inv.take(i).expect("Expected item to exist in inventory"); + }) + }); + + for i in 0..self.output.1 { + if let Some(item) = inv.push(self.output.0.clone()) { + return Ok(Some((item, self.output.1 - i))); + } + } + + Ok(None) + } + + pub fn inputs(&self) -> impl ExactSizeIterator { + self.inputs.iter().map(|(item, amount)| (item, *amount)) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RecipeBook { + recipes: HashMap, +} + +impl RecipeBook { + pub fn get(&self, recipe: &str) -> Option<&Recipe> { self.recipes.get(recipe) } + + pub fn iter(&self) -> impl ExactSizeIterator { self.recipes.iter() } + + pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> { + self.recipes + .iter() + .filter(|(_, recipe)| inv.contains_ingredients(recipe).is_ok()) + .map(|(name, recipe)| (name.clone(), recipe.clone())) + .collect() + } +} + +impl Asset for RecipeBook { + const ENDINGS: &'static [&'static str] = &["ron"]; + + fn parse(buf_reader: BufReader) -> Result { + ron::de::from_reader::< + BufReader, + HashMap)>, + >(buf_reader) + .map_err(assets::Error::parse_error) + .and_then(|recipes| { + Ok(RecipeBook { + recipes: recipes + .into_iter() + .map::, _>( + |(name, ((output, amount), inputs))| { + Ok((name, Recipe { + output: ((&*assets::load::(&output)?).clone(), amount), + inputs: inputs + .into_iter() + .map::, _>( + |(name, amount)| { + Ok(((&*assets::load::(&name)?).clone(), amount)) + }, + ) + .collect::>()?, + })) + }, + ) + .collect::>()?, + }) + }) + } +} + +pub fn default_recipe_book() -> Arc { assets::load_expect("common.recipe_book") } diff --git a/common/src/spiral.rs b/common/src/spiral.rs index 5a13b93d9d..2a76a3484d 100644 --- a/common/src/spiral.rs +++ b/common/src/spiral.rs @@ -16,6 +16,7 @@ impl Spiral2d { impl Iterator for Spiral2d { type Item = Vec2; + #[allow(clippy::erasing_op)] fn next(&mut self) -> Option { let layer_size = (self.layer * 8 + 4 * self.layer.min(1) - 4).max(1); if self.i >= layer_size { diff --git a/common/src/state.rs b/common/src/state.rs index edae2d01b0..f232d07163 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -249,15 +249,11 @@ impl State { /// Removes every chunk of the terrain. pub fn clear_terrain(&mut self) { - let keys = self - .terrain_mut() - .drain() - .map(|(key, _)| key) - .collect::>(); + let removed_chunks = &mut self.ecs.write_resource::().removed_chunks; - for key in keys { - self.remove_chunk(key); - } + self.terrain_mut().drain().for_each(|(key, _)| { + removed_chunks.insert(key); + }); } /// Insert the provided chunk into this state's terrain. diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index 5b1f7cf0d7..b41bd6c208 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -54,7 +54,7 @@ impl CharacterBehavior for Data { } else if !self.exhausted { // Fire let mut projectile = self.projectile.clone(); - projectile.set_owner(*data.uid); + projectile.owner = Some(*data.uid); update.server_events.push_front(ServerEvent::Shoot { entity: data.entity, dir: data.inputs.look_dir, diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs new file mode 100644 index 0000000000..7014f8de58 --- /dev/null +++ b/common/src/states/charged_ranged.rs @@ -0,0 +1,192 @@ +use crate::{ + comp::{ + projectile, Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, + StateUpdate, + }, + event::ServerEvent, + states::utils::*, + sys::character_behavior::*, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +const MAX_GRAVITY: f32 = 0.2; +const MIN_GRAVITY: f32 = 0.05; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Whether the attack fired already + pub exhausted: bool, + /// How much energy is drained per second when charging + pub energy_drain: u32, + /// How much damage is dealt with no charge + pub initial_damage: u32, + /// How much damage is dealt with max charge + pub max_damage: u32, + /// How much knockback there is with no chage + pub initial_knockback: f32, + /// How much knockback there is at max charge + pub max_knockback: f32, + /// How long the weapon needs to be prepared for + pub prepare_duration: Duration, + /// How long it takes to charge the weapon to max damage and knockback + pub charge_duration: Duration, + /// How long the state has been charging + pub charge_timer: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, + /// Projectile information + pub projectile_body: Body, + pub projectile_light: Option, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + + handle_move(data, &mut update, 0.3); + handle_jump(data, &mut update); + + if self.prepare_duration != Duration::default() { + // Prepare (draw the bow) + update.character = CharacterState::ChargedRanged(Data { + exhausted: self.exhausted, + energy_drain: self.energy_drain, + initial_damage: self.initial_damage, + max_damage: self.max_damage, + initial_knockback: self.initial_knockback, + max_knockback: self.max_knockback, + prepare_duration: self + .prepare_duration + .checked_sub(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + charge_duration: self.charge_duration, + charge_timer: self.charge_timer, + recover_duration: self.recover_duration, + projectile_body: self.projectile_body, + projectile_light: self.projectile_light, + }); + } else if data.inputs.secondary.is_pressed() + && self.charge_timer < self.charge_duration + && update.energy.current() > 0 + { + // Charge the bow + update.character = CharacterState::ChargedRanged(Data { + exhausted: self.exhausted, + energy_drain: self.energy_drain, + initial_damage: self.initial_damage, + max_damage: self.max_damage, + initial_knockback: self.initial_knockback, + max_knockback: self.max_knockback, + prepare_duration: self.prepare_duration, + charge_timer: self + .charge_timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + charge_duration: self.charge_duration, + recover_duration: self.recover_duration, + projectile_body: self.projectile_body, + projectile_light: self.projectile_light, + }); + + // Consumes energy if there's enough left and RMB is held down + update.energy.change_by( + -(self.energy_drain as f32 * data.dt.0) as i32, + EnergySource::Ability, + ); + } else if data.inputs.secondary.is_pressed() { + // Charge the bow + update.character = CharacterState::ChargedRanged(Data { + exhausted: self.exhausted, + energy_drain: self.energy_drain, + initial_damage: self.initial_damage, + max_damage: self.max_damage, + initial_knockback: self.initial_knockback, + max_knockback: self.max_knockback, + prepare_duration: self.prepare_duration, + charge_timer: self.charge_timer, + charge_duration: self.charge_duration, + recover_duration: self.recover_duration, + projectile_body: self.projectile_body, + projectile_light: self.projectile_light, + }); + + // Consumes energy if there's enough left and RMB is held down + update.energy.change_by( + -(self.energy_drain as f32 * data.dt.0 / 5.0) as i32, + EnergySource::Ability, + ); + } else if !self.exhausted { + let charge_amount = + (self.charge_timer.as_secs_f32() / self.charge_duration.as_secs_f32()).min(1.0); + // Fire + let mut projectile = Projectile { + hit_solid: vec![projectile::Effect::Stick], + hit_entity: vec![ + projectile::Effect::Damage( + -(self.initial_damage as i32 + + (charge_amount * (self.max_damage - self.initial_damage) as f32) + as i32), + ), + projectile::Effect::Knockback( + self.initial_knockback + + charge_amount * (self.max_knockback - self.initial_knockback), + ), + projectile::Effect::Vanish, + ], + time_left: Duration::from_secs(15), + owner: None, + }; + projectile.owner = Some(*data.uid); + update.server_events.push_front(ServerEvent::Shoot { + entity: data.entity, + dir: data.inputs.look_dir, + body: self.projectile_body, + projectile, + light: self.projectile_light, + gravity: Some(Gravity( + MAX_GRAVITY - charge_amount * (MAX_GRAVITY - MIN_GRAVITY), + )), + }); + + update.character = CharacterState::ChargedRanged(Data { + exhausted: true, + energy_drain: self.energy_drain, + initial_damage: self.initial_damage, + max_damage: self.max_damage, + initial_knockback: self.initial_knockback, + max_knockback: self.max_knockback, + prepare_duration: self.prepare_duration, + charge_timer: self.charge_timer, + charge_duration: self.charge_duration, + recover_duration: self.recover_duration, + projectile_body: self.projectile_body, + projectile_light: self.projectile_light, + }); + } else if self.recover_duration != Duration::default() { + // Recovery + update.character = CharacterState::ChargedRanged(Data { + exhausted: self.exhausted, + energy_drain: self.energy_drain, + initial_damage: self.initial_damage, + max_damage: self.max_damage, + initial_knockback: self.initial_knockback, + max_knockback: self.max_knockback, + prepare_duration: self.prepare_duration, + charge_timer: self.charge_timer, + charge_duration: self.charge_duration, + recover_duration: self + .recover_duration + .checked_sub(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + projectile_body: self.projectile_body, + projectile_light: self.projectile_light, + }); + } else { + // Done + update.character = CharacterState::Wielding; + } + + update + } +} diff --git a/common/src/states/glide_wield.rs b/common/src/states/glide_wield.rs index 8efd033c75..e385a3d3a0 100644 --- a/common/src/states/glide_wield.rs +++ b/common/src/states/glide_wield.rs @@ -12,6 +12,8 @@ impl CharacterBehavior for Data { handle_move(&data, &mut update, 1.0); handle_jump(&data, &mut update); + handle_dodge_input(data, &mut update); + handle_wield(data, &mut update); // If not on the ground while wielding glider enter gliding state if !data.physics.on_ground && !data.physics.in_fluid { diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 35676a2d17..b88bfe7514 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -2,6 +2,7 @@ pub mod basic_block; pub mod basic_melee; pub mod basic_ranged; pub mod boost; +pub mod charged_ranged; pub mod climb; pub mod dance; pub mod dash_melee; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 7ebec30dc4..ce01809f80 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -31,9 +31,9 @@ impl Body { pub fn base_accel(&self) -> f32 { match self { Body::Humanoid(_) => 100.0, - Body::QuadrupedSmall(_) => 80.0, + Body::QuadrupedSmall(_) => 85.0, Body::QuadrupedMedium(_) => 180.0, - Body::BirdMedium(_) => 70.0, + Body::BirdMedium(_) => 80.0, Body::FishMedium(_) => 50.0, Body::Dragon(_) => 250.0, Body::BirdSmall(_) => 75.0, @@ -41,7 +41,7 @@ impl Body { Body::BipedLarge(_) => 120.0, Body::Object(_) => 40.0, Body::Golem(_) => 130.0, - Body::Critter(_) => 65.0, + Body::Critter(_) => 85.0, Body::QuadrupedLow(_) => 120.0, } } diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs index c63f0a3d95..aa723fce2c 100644 --- a/common/src/states/wielding.rs +++ b/common/src/states/wielding.rs @@ -39,6 +39,12 @@ impl CharacterBehavior for Data { update } + fn glide_wield(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_glide_wield(data, &mut update); + update + } + fn swap_loadout(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_swap_loadout(data, &mut update); diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 5fcfd5d47a..36b8afd4e9 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -3,11 +3,11 @@ use crate::{ self, agent::Activity, item::{tool::ToolKind, ItemKind}, - Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState, - Ori, Pos, Scale, Stats, Vel, + Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout, + MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel, }, event::{EventBus, ServerEvent}, - path::Chaser, + path::{Chaser, TraversalConfig}, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, terrain::TerrainGrid, @@ -38,9 +38,11 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Stats>, ReadStorage<'a, Loadout>, ReadStorage<'a, CharacterState>, + ReadStorage<'a, PhysicsState>, ReadStorage<'a, Uid>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, + ReadStorage<'a, Body>, WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, ReadStorage<'a, MountState>, @@ -62,9 +64,11 @@ impl<'a> System<'a> for Sys { stats, loadouts, character_states, + physics_states, uids, terrain, alignments, + bodies, mut agents, mut controllers, mount_states, @@ -78,6 +82,8 @@ impl<'a> System<'a> for Sys { alignment, loadout, character_state, + physics_state, + body, uid, agent, controller, @@ -90,6 +96,8 @@ impl<'a> System<'a> for Sys { alignments.maybe(), &loadouts, &character_states, + &physics_states, + bodies.maybe(), &uids, &mut agents, &mut controllers, @@ -126,7 +134,8 @@ impl<'a> System<'a> for Sys { // and so can afford to be less precise when trying to move around // the world (especially since they would otherwise get stuck on // obstacles that smaller entities would not). - let traversal_tolerance = scale + vel.0.magnitude() * 0.3; + let node_tolerance = scale + vel.0.xy().magnitude() * 0.2; + let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0); let mut do_idle = false; let mut choose_target = false; @@ -199,8 +208,12 @@ impl<'a> System<'a> for Sys { pos.0, vel.0, tgt_pos.0, - AVG_FOLLOW_DIST, - traversal_tolerance, + TraversalConfig { + node_tolerance, + slow_factor, + on_ground: physics_state.on_ground, + min_tgt_dist: AVG_FOLLOW_DIST, + }, ) { inputs.move_dir = bearing.xy().try_normalized().unwrap_or(Vec2::zero()) @@ -315,8 +328,12 @@ impl<'a> System<'a> for Sys { pos.0, vel.0, tgt_pos.0, - 1.25, - traversal_tolerance, + TraversalConfig { + node_tolerance, + slow_factor, + on_ground: physics_state.on_ground, + min_tgt_dist: 1.25, + }, ) { inputs.move_dir = Vec2::from(bearing) .try_normalized() diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs index 496a3f2207..e79d31aa29 100644 --- a/common/src/sys/character_behavior.rs +++ b/common/src/sys/character_behavior.rs @@ -245,6 +245,7 @@ impl<'a> System<'a> for Sys { CharacterState::DashMelee(data) => data.handle_event(&j, action), CharacterState::LeapMelee(data) => data.handle_event(&j, action), CharacterState::SpinMelee(data) => data.handle_event(&j, action), + CharacterState::ChargedRanged(data) => data.handle_event(&j, action), }; local_emitter.append(&mut state_update.local_events); server_emitter.append(&mut state_update.server_events); @@ -271,6 +272,7 @@ impl<'a> System<'a> for Sys { CharacterState::DashMelee(data) => data.behavior(&j), CharacterState::LeapMelee(data) => data.behavior(&j), CharacterState::SpinMelee(data) => data.behavior(&j), + CharacterState::ChargedRanged(data) => data.behavior(&j), }; local_emitter.append(&mut state_update.local_events); diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index f0e84e65bb..d585171b0d 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - Alignment, Attacking, Body, CharacterState, HealthChange, HealthSource, Ori, Pos, Scale, - Stats, + Alignment, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, + HealthSource, Loadout, Ori, Pos, Scale, Stats, }, event::{EventBus, LocalEvent, ServerEvent}, sync::Uid, @@ -29,6 +29,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Alignment>, ReadStorage<'a, Body>, ReadStorage<'a, Stats>, + ReadStorage<'a, Loadout>, WriteStorage<'a, Attacking>, WriteStorage<'a, CharacterState>, ); @@ -46,6 +47,7 @@ impl<'a> System<'a> for Sys { alignments, bodies, stats, + loadouts, mut attacking_storage, character_states, ): Self::SystemData, @@ -110,7 +112,15 @@ impl<'a> System<'a> for Sys { && ori2.angle_between(pos_b2 - pos2) < attack.max_angle + (rad_b / pos2.distance(pos_b2)).atan() { // Weapon gives base damage - let mut healthchange = attack.base_healthchange as f32; + let source = if attack.base_healthchange > 0 { + DamageSource::Healing + } else { + DamageSource::Melee + }; + let mut damage = Damage { + healthchange: attack.base_healthchange as f32, + source, + }; let mut knockback = attack.knockback; // TODO: remove this, either it will remain unused or be used as a temporary @@ -122,34 +132,30 @@ impl<'a> System<'a> for Sys { // TODO: remove this when there is a better way to deal with alignment // Don't heal NPCs - if (healthchange > 0.0 && alignment_b_maybe + if (damage.healthchange > 0.0 && alignment_b_maybe .map(|a| !a.is_friendly_to_players()) .unwrap_or(true)) // Don't hurt pets - || (healthchange < 0.0 && alignment_b_maybe + || (damage.healthchange < 0.0 && alignment_b_maybe .map(|b| Alignment::Owned(*uid).passive_towards(*b)) .unwrap_or(false)) { - healthchange = 0.0; + damage.healthchange = 0.0; knockback = 0.0; } - if rand::random() { - healthchange *= 1.2; + let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) + && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; + + if let Some(loadout) = loadouts.get(b) { + damage.modify_damage(block, loadout); } - // Block - if character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0 - { - healthchange *= 1.0 - BLOCK_EFFICIENCY - } - - if healthchange != 0.0 { + if damage.healthchange != 0.0 { server_emitter.emit(ServerEvent::Damage { uid: *uid_b, change: HealthChange { - amount: healthchange as i32, + amount: damage.healthchange as i32, cause: HealthSource::Attack { by: *uid }, }, }); @@ -157,7 +163,7 @@ impl<'a> System<'a> for Sys { if knockback != 0.0 { local_emitter.emit(LocalEvent::ApplyForce { entity: b, - force: attack.knockback + force: knockback * *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5), }); } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index d57ef92df1..29242556d4 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -117,7 +117,12 @@ impl<'a> System<'a> for Sys { } else { 0.0 }); - let downward_force = if physics_state.in_fluid { + let in_loaded_chunk = terrain + .get_key(terrain.pos_key(pos.0.map(|e| e.floor() as i32))) + .is_some(); + let downward_force = if !in_loaded_chunk { + 0.0 // No gravity in unloaded chunks + } else if physics_state.in_fluid { (1.0 - BOUYANCY) * GRAVITY } else { GRAVITY @@ -125,10 +130,7 @@ impl<'a> System<'a> for Sys { vel.0 = integrate_forces(dt.0, vel.0, downward_force, friction); // Don't move if we're not in a loaded chunk - let mut pos_delta = if terrain - .get_key(terrain.pos_key(pos.0.map(|e| e.floor() as i32))) - .is_some() - { + let mut pos_delta = if in_loaded_chunk { // this is an approximation that allows most framerates to // behave in a similar manner. let dt_lerp = 0.2; @@ -319,6 +321,7 @@ impl<'a> System<'a> for Sys { } if attempts == MAX_ATTEMPTS { + vel.0 = Vec3::zero(); pos.0 = old_pos; break; } diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 306b0bab10..ad2e4a1023 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - projectile, Alignment, Energy, EnergySource, HealthSource, Ori, PhysicsState, Pos, - Projectile, Vel, + projectile, Alignment, Damage, DamageSource, Energy, EnergySource, HealthChange, + HealthSource, Loadout, Ori, PhysicsState, Pos, Projectile, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, state::DeltaTime, @@ -29,6 +29,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Projectile>, WriteStorage<'a, Energy>, ReadStorage<'a, Alignment>, + ReadStorage<'a, Loadout>, ); fn run( @@ -46,6 +47,7 @@ impl<'a> System<'a> for Sys { mut projectiles, mut energies, alignments, + loadouts, ): Self::SystemData, ) { let mut local_emitter = local_bus.emitter(); @@ -84,8 +86,19 @@ impl<'a> System<'a> for Sys { else if let Some(other) = physics.touch_entity { for effect in projectile.hit_entity.drain(..) { match effect { - projectile::Effect::Damage(change) => { + projectile::Effect::Damage(healthchange) => { let owner_uid = projectile.owner.unwrap(); + let mut damage = Damage { + healthchange: healthchange as f32, + source: DamageSource::Projectile, + }; + if let Some(entity) = + uid_allocator.retrieve_entity_internal(other.into()) + { + if let Some(loadout) = loadouts.get(entity) { + damage.modify_damage(false, loadout); + } + } // Hacky: remove this when groups get implemented let passive = uid_allocator .retrieve_entity_internal(other.into()) @@ -96,7 +109,13 @@ impl<'a> System<'a> for Sys { }) .unwrap_or(false); if other != projectile.owner.unwrap() && !passive { - server_emitter.emit(ServerEvent::Damage { uid: other, change }); + server_emitter.emit(ServerEvent::Damage { + uid: other, + change: HealthChange { + amount: damage.healthchange as i32, + cause: HealthSource::Attack { by: owner_uid }, + }, + }); } }, projectile::Effect::Knockback(knockback) => { diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index f6621cd34c..3c83d45bb9 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -105,7 +105,8 @@ impl<'a> System<'a> for Sys { | CharacterState::LeapMelee { .. } | CharacterState::SpinMelee { .. } | CharacterState::TripleStrike { .. } - | CharacterState::BasicRanged { .. } => { + | CharacterState::BasicRanged { .. } + | CharacterState::ChargedRanged { .. } => { if energy.get_unchecked().regen_rate != 0.0 { energy.get_mut_unchecked().regen_rate = 0.0 } diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 6c69a9ee35..bf6886c0ca 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -83,6 +83,9 @@ pub enum BlockKind { WardrobeDouble, LargeGrass, Pot, + Stones, + Twigs, + ShinyGem, } impl BlockKind { @@ -167,6 +170,9 @@ impl BlockKind { BlockKind::WardrobeSingle => false, BlockKind::WardrobeDouble => false, BlockKind::Pot => false, + BlockKind::Stones => true, + BlockKind::Twigs => true, + BlockKind::ShinyGem => true, _ => false, } } @@ -252,6 +258,9 @@ impl BlockKind { BlockKind::WardrobeDouble => false, BlockKind::LargeGrass => false, BlockKind::Pot => false, + BlockKind::Stones => false, + BlockKind::Twigs => false, + BlockKind::ShinyGem => false, _ => true, } } @@ -323,6 +332,9 @@ impl BlockKind { BlockKind::WardrobeSingle => true, BlockKind::WardrobeDouble => true, BlockKind::Pot => true, + BlockKind::Stones => false, + BlockKind::Twigs => false, + BlockKind::ShinyGem => false, _ => true, } } @@ -389,6 +401,9 @@ impl BlockKind { BlockKind::VeloriteFrag => true, BlockKind::Chest => true, BlockKind::Coconut => true, + BlockKind::Stones => true, + BlockKind::Twigs => true, + BlockKind::ShinyGem => true, _ => false, } } diff --git a/network/Cargo.toml b/network/Cargo.toml index 72bdde9ebc..d6c271ee48 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -6,6 +6,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +metrics = ["prometheus"] + +default = ["metrics"] + [dependencies] lz4-compress = "0.1.1" @@ -19,7 +25,7 @@ async-std = { version = "~1.5", default-features = false, features = ["std", "as #tracing and metrics tracing = { version = "0.1", default-features = false } tracing-futures = "0.2" -prometheus = { version = "0.9", default-features = false } +prometheus = { version = "0.9", default-features = false, optional = true } #async futures = { version = "0.3", features = ["thread-pool"] } #mpsc channel registry diff --git a/network/examples/chat/Cargo.lock b/network/examples/chat/Cargo.lock index 9c26fd8f98..c5efb50889 100644 --- a/network/examples/chat/Cargo.lock +++ b/network/examples/chat/Cargo.lock @@ -65,7 +65,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" dependencies = [ - "byteorder", + "byteorder 1.3.4", "serde", ] @@ -75,6 +75,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + [[package]] name = "byteorder" version = "1.3.4" @@ -345,6 +351,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lz4-compress" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f966533a922a9bba9e95e594c1fdb3b9bf5fdcdb11e37e51ad84cd76e468b91" +dependencies = [ + "byteorder 0.5.3", + "quick-error", +] + [[package]] name = "matchers" version = "0.0.1" @@ -538,15 +554,15 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5567486d5778e2c6455b1b90ff1c558f29e751fc018130fa182e15828e728af1" +checksum = "dd0ced56dee39a6e960c15c74dc48849d614586db2eaada6497477af7c7811cd" dependencies = [ "cfg-if", "fnv", "lazy_static", - "quick-error", "spin", + "thiserror", ] [[package]] @@ -629,7 +645,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ - "byteorder", + "byteorder 1.3.4", "regex-syntax", ] @@ -712,6 +728,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -798,6 +834,7 @@ dependencies = [ "crossbeam-channel", "futures", "lazy_static", + "lz4-compress", "prometheus", "rand", "serde", diff --git a/network/examples/chat/src/main.rs b/network/examples/chat/src/main.rs index a3a4fabcab..89ca24c054 100644 --- a/network/examples/chat/src/main.rs +++ b/network/examples/chat/src/main.rs @@ -4,9 +4,10 @@ //! (cd network/examples/chat && RUST_BACKTRACE=1 cargo run --release -- --trace=info --port 15006 --mode=client) //! ``` use async_std::io; +use async_std::sync::RwLock; use clap::{App, Arg}; use futures::executor::{block_on, ThreadPool}; -use network::{Address, Network, Participant, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; +use network::{ProtocolAddr, Network, Participant, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; use std::{sync::Arc, thread, time::Duration}; use tracing::*; use tracing_subscriber::EnvFilter; @@ -76,8 +77,8 @@ fn main() { let port: u16 = matches.value_of("port").unwrap().parse().unwrap(); let ip: &str = matches.value_of("ip").unwrap(); let address = match matches.value_of("protocol") { - Some("tcp") => Address::Tcp(format!("{}:{}", ip, port).parse().unwrap()), - Some("udp") => Address::Udp(format!("{}:{}", ip, port).parse().unwrap()), + Some("tcp") => ProtocolAddr::Tcp(format!("{}:{}", ip, port).parse().unwrap()), + Some("udp") => ProtocolAddr::Udp(format!("{}:{}", ip, port).parse().unwrap()), _ => panic!("invalid mode, run --help!"), }; @@ -98,22 +99,24 @@ fn main() { } } -fn server(address: Address) { - let (server, f) = Network::new(Pid::new(), None); +fn server(address: ProtocolAddr) { + let (server, f) = Network::new(Pid::new()); let server = Arc::new(server); std::thread::spawn(f); let pool = ThreadPool::new().unwrap(); + let participants = Arc::new(RwLock::new(Vec::new())); block_on(async { server.listen(address).await.unwrap(); loop { - let p1 = server.connected().await.unwrap(); + let p1 = Arc::new(server.connected().await.unwrap()); let server1 = server.clone(); - pool.spawn_ok(client_connection(server1, p1)); + participants.write().await.push(p1.clone()); + pool.spawn_ok(client_connection(server1, p1, participants.clone())); } }); } -async fn client_connection(network: Arc, participant: Arc) { +async fn client_connection(_network: Arc, participant: Arc, participants: Arc>>>) { let mut s1 = participant.opened().await.unwrap(); let username = s1.recv::().await.unwrap(); println!("[{}] connected", username); @@ -124,14 +127,12 @@ async fn client_connection(network: Arc, participant: Arc) }, Ok(msg) => { println!("[{}]: {}", username, msg); - let mut parts = network.participants().await; - for (_, p) in parts.drain() { + for p in participants.read().await.iter() { match p .open(32, PROMISES_ORDERED | PROMISES_CONSISTENCY) .await { Err(_) => { - //probably disconnected, remove it - network.disconnect(p).await.unwrap(); + info!("error talking to client, //TODO drop it") }, Ok(mut s) => s.send((username.clone(), msg.clone())).unwrap(), }; @@ -142,8 +143,8 @@ async fn client_connection(network: Arc, participant: Arc) println!("[{}] disconnected", username); } -fn client(address: Address) { - let (client, f) = Network::new(Pid::new(), None); +fn client(address: ProtocolAddr) { + let (client, f) = Network::new(Pid::new()); std::thread::spawn(f); let pool = ThreadPool::new().unwrap(); @@ -180,7 +181,7 @@ fn client(address: Address) { // receiving i open and close a stream per message. this can be done easier but // this allows me to be quite lazy on the server side and just get a list of // all participants and send to them... -async fn read_messages(participant: Arc) { +async fn read_messages(participant: Participant) { while let Ok(mut s) = participant.opened().await { let (username, message) = s.recv::<(String, String)>().await.unwrap(); println!("[{}]: {}", username, message); diff --git a/network/examples/fileshare/Cargo.lock b/network/examples/fileshare/Cargo.lock index 0144ec3ac0..24eaae51a1 100644 --- a/network/examples/fileshare/Cargo.lock +++ b/network/examples/fileshare/Cargo.lock @@ -83,7 +83,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" dependencies = [ - "byteorder", + "byteorder 1.3.4", "serde", ] @@ -104,6 +104,12 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + [[package]] name = "byteorder" version = "1.3.4" @@ -418,6 +424,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lz4-compress" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f966533a922a9bba9e95e594c1fdb3b9bf5fdcdb11e37e51ad84cd76e468b91" +dependencies = [ + "byteorder 0.5.3", + "quick-error", +] + [[package]] name = "matchers" version = "0.0.1" @@ -597,15 +613,15 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5567486d5778e2c6455b1b90ff1c558f29e751fc018130fa182e15828e728af1" +checksum = "dd0ced56dee39a6e960c15c74dc48849d614586db2eaada6497477af7c7811cd" dependencies = [ "cfg-if", "fnv", "lazy_static", - "quick-error", "spin", + "thiserror", ] [[package]] @@ -699,7 +715,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ - "byteorder", + "byteorder 1.3.4", "regex-syntax", ] @@ -803,6 +819,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -889,6 +925,7 @@ dependencies = [ "crossbeam-channel", "futures", "lazy_static", + "lz4-compress", "prometheus", "rand", "serde", diff --git a/network/examples/fileshare/src/commands.rs b/network/examples/fileshare/src/commands.rs index 99178ea018..9d92f00d95 100644 --- a/network/examples/fileshare/src/commands.rs +++ b/network/examples/fileshare/src/commands.rs @@ -2,17 +2,17 @@ use async_std::{ fs, path::{Path, PathBuf}, }; -use network::{Address, Participant, Stream}; +use network::{ProtocolAddr, Participant, Stream}; use rand::Rng; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; #[derive(Debug)] pub enum LocalCommand { Shutdown, Disconnect, - Connect(Address), + Connect(ProtocolAddr), List, Serve(FileInfo), Get(u32, Option), @@ -34,7 +34,7 @@ pub struct FileInfo { pub struct RemoteInfo { infos: HashMap, - _participant: Arc, + _participant: Participant, pub cmd_out: Stream, pub file_out: Stream, } @@ -44,7 +44,7 @@ impl FileInfo { let mt = match fs::metadata(&path).await { Err(e) => { println!( - "cannot get metadata for file: {:?}, does it exist? Error: {:?}", + "Cannot get metadata for file: {:?}, does it exist? Error: {:?}", &path, &e ); return None; @@ -68,7 +68,7 @@ impl FileInfo { } impl RemoteInfo { - pub fn new(cmd_out: Stream, file_out: Stream, participant: Arc) -> Self { + pub fn new(cmd_out: Stream, file_out: Stream, participant: Participant) -> Self { Self { infos: HashMap::new(), _participant: participant, diff --git a/network/examples/fileshare/src/main.rs b/network/examples/fileshare/src/main.rs index 5647dfaf07..a0fde1a622 100644 --- a/network/examples/fileshare/src/main.rs +++ b/network/examples/fileshare/src/main.rs @@ -10,7 +10,7 @@ use futures::{ executor::{block_on, ThreadPool}, sink::SinkExt, }; -use network::Address; +use network::ProtocolAddr; use std::{thread, time::Duration}; use tracing::*; use tracing_subscriber::EnvFilter; @@ -54,7 +54,7 @@ fn main() { .init(); let port: u16 = matches.value_of("port").unwrap().parse().unwrap(); - let address = Address::Tcp(format!("{}:{}", "127.0.0.1", port).parse().unwrap()); + let address = ProtocolAddr::Tcp(format!("{}:{}", "127.0.0.1", port).parse().unwrap()); let (server, cmd_sender) = Server::new(); let pool = ThreadPool::new().unwrap(); @@ -70,7 +70,7 @@ fn file_exists(file: String) -> Result<(), String> { if file.exists() { Ok(()) } else { - Err(format!("file does not exist")) + Err(format!("File does not exist")) } } @@ -157,13 +157,13 @@ async fn client(mut cmd_sender: mpsc::UnboundedSender) { ("connect", Some(connect_matches)) => { let socketaddr = connect_matches.value_of("ip:port").unwrap().parse().unwrap(); cmd_sender - .send(LocalCommand::Connect(Address::Tcp(socketaddr))) + .send(LocalCommand::Connect(ProtocolAddr::Tcp(socketaddr))) .await .unwrap(); }, ("t", _) => { cmd_sender - .send(LocalCommand::Connect(Address::Tcp( + .send(LocalCommand::Connect(ProtocolAddr::Tcp( "127.0.0.1:1231".parse().unwrap(), ))) .await diff --git a/network/examples/fileshare/src/server.rs b/network/examples/fileshare/src/server.rs index f6312a58b1..a94a1e668c 100644 --- a/network/examples/fileshare/src/server.rs +++ b/network/examples/fileshare/src/server.rs @@ -5,7 +5,7 @@ use async_std::{ sync::{Mutex, RwLock}, }; use futures::{channel::mpsc, future::FutureExt, stream::StreamExt}; -use network::{Address, Network, Participant, Pid, Stream, PROMISES_CONSISTENCY, PROMISES_ORDERED}; +use network::{ProtocolAddr, Network, Participant, Pid, Stream, PROMISES_CONSISTENCY, PROMISES_ORDERED}; use std::{collections::HashMap, sync::Arc}; use tracing::*; @@ -26,7 +26,7 @@ impl Server { pub fn new() -> (Self, mpsc::UnboundedSender) { let (command_sender, command_receiver) = mpsc::unbounded(); - let (network, f) = Network::new(Pid::new(), None); + let (network, f) = Network::new(Pid::new()); std::thread::spawn(f); let run_channels = Some(ControlChannels { command_receiver }); @@ -42,7 +42,7 @@ impl Server { ) } - pub async fn run(mut self, address: Address) { + pub async fn run(mut self, address: ProtocolAddr) { let run_channels = self.run_channels.take().unwrap(); self.network.listen(address).await.unwrap(); @@ -54,34 +54,31 @@ impl Server { } async fn command_manager(&self, command_receiver: mpsc::UnboundedReceiver) { - trace!("start command_manager"); + trace!("Start command_manager"); command_receiver .for_each_concurrent(None, async move |cmd| { match cmd { LocalCommand::Shutdown => { - println!("shutting down service"); + println!("Shutting down service"); return; }, LocalCommand::Disconnect => { self.remotes.write().await.clear(); - for (_, p) in self.network.participants().await.drain() { - self.network.disconnect(p).await.unwrap(); - } - println!("disconnecting all connections"); + println!("Disconnecting all connections"); return; }, LocalCommand::Connect(addr) => { - println!("trying to connect to: {:?}", &addr); + println!("Trying to connect to: {:?}", &addr); match self.network.connect(addr.clone()).await { Ok(p) => self.loop_participant(p).await, Err(e) => { - println!("failled to connect to {:?}, err: {:?}", &addr, e); + println!("Failled to connect to {:?}, err: {:?}", &addr, e); }, } }, LocalCommand::Serve(fileinfo) => { self.served.write().await.push(fileinfo.clone()); - println!("serving file: {:?}", fileinfo.path); + println!("Serving file: {:?}", fileinfo.path); }, LocalCommand::List => { let mut total_file_infos = vec![]; @@ -110,11 +107,11 @@ impl Server { } }) .await; - trace!("stop command_manager"); + trace!("Stop command_manager"); } async fn connect_manager(&self) { - trace!("start connect_manager"); + trace!("Start connect_manager"); let iter = futures::stream::unfold((), |_| { self.network.connected().map(|r| r.ok().map(|v| (v, ()))) }); @@ -123,17 +120,17 @@ impl Server { self.loop_participant(participant).await; }) .await; - trace!("stop connect_manager"); + trace!("Stop connect_manager"); } - async fn loop_participant(&self, p: Arc) { + async fn loop_participant(&self, p: Participant) { if let (Ok(cmd_out), Ok(file_out), Ok(cmd_in), Ok(file_in)) = ( p.open(15, PROMISES_CONSISTENCY | PROMISES_ORDERED).await, p.open(40, PROMISES_CONSISTENCY).await, p.opened().await, p.opened().await, ) { - debug!(?p, "connection successfully initiated"); + debug!(?p, "Connection successfully initiated"); let id = p.remote_pid(); let ri = Arc::new(Mutex::new(RemoteInfo::new(cmd_out, file_out, p))); self.remotes.write().await.insert(id, ri.clone()); @@ -146,24 +143,24 @@ impl Server { async fn handle_remote_cmd(&self, mut stream: Stream, remote_info: Arc>) { while let Ok(msg) = stream.recv::().await { - println!("got message: {:?}", &msg); + println!("Got message: {:?}", &msg); match msg { Command::List => { - info!("request to send my list"); + info!("Request to send my list"); let served = self.served.read().await.clone(); stream.send(served).unwrap(); }, Command::Get(id) => { for file_info in self.served.read().await.iter() { if file_info.id() == id { - info!("request to send file i got, sending it"); + info!("Request to send file i got, sending it"); if let Ok(data) = file_info.load().await { match remote_info.lock().await.file_out.send((file_info, data)) { Ok(_) => debug!("send file"), Err(e) => error!(?e, "sending file failed"), } } else { - warn!("cannot send file as loading failed, oes it still exist?"); + warn!("Cannot send file as loading failed, oes it still exist?"); } } } @@ -174,18 +171,18 @@ impl Server { async fn handle_files(&self, mut stream: Stream, _remote_info: Arc>) { while let Ok((fi, data)) = stream.recv::<(FileInfo, Vec)>().await { - debug!(?fi, "got file"); + debug!(?fi, "Got file"); let path = self.receiving_files.lock().await.remove(&fi.id()).flatten(); let path: PathBuf = match &path { Some(path) => shellexpand::tilde(&path).parse().unwrap(), None => { let mut path = std::env::current_dir().unwrap(); path.push(fi.path().file_name().unwrap()); - trace!("no path provided, saving down to {:?}", path); + trace!("No path provided, saving down to {:?}", path); PathBuf::from(path) }, }; - debug!("received file, going to save it under {:?}", path); + debug!("Received file, going to save it under {:?}", path); fs::write(path, data).await.unwrap(); } } diff --git a/network/examples/network-speed/Cargo.lock b/network/examples/network-speed/Cargo.lock index 7cd1b8e982..3a1ba21cb5 100644 --- a/network/examples/network-speed/Cargo.lock +++ b/network/examples/network-speed/Cargo.lock @@ -71,7 +71,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" dependencies = [ - "byteorder", + "byteorder 1.3.4", "serde", ] @@ -81,6 +81,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + [[package]] name = "byteorder" version = "1.3.4" @@ -372,6 +378,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lz4-compress" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f966533a922a9bba9e95e594c1fdb3b9bf5fdcdb11e37e51ad84cd76e468b91" +dependencies = [ + "byteorder 0.5.3", + "quick-error", +] + [[package]] name = "matchers" version = "0.0.1" @@ -578,16 +594,16 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5567486d5778e2c6455b1b90ff1c558f29e751fc018130fa182e15828e728af1" +checksum = "dd0ced56dee39a6e960c15c74dc48849d614586db2eaada6497477af7c7811cd" dependencies = [ "cfg-if", "fnv", "lazy_static", "protobuf", - "quick-error", "spin", + "thiserror", ] [[package]] @@ -670,7 +686,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ - "byteorder", + "byteorder 1.3.4", "regex-syntax", ] @@ -753,6 +769,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -880,6 +916,7 @@ dependencies = [ "crossbeam-channel", "futures", "lazy_static", + "lz4-compress", "prometheus", "rand", "serde", diff --git a/network/examples/network-speed/src/main.rs b/network/examples/network-speed/src/main.rs index 3e702ae2ce..f182b84483 100644 --- a/network/examples/network-speed/src/main.rs +++ b/network/examples/network-speed/src/main.rs @@ -7,7 +7,7 @@ mod metrics; use clap::{App, Arg}; use futures::executor::block_on; -use network::{Address, MessageBuffer, Network, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; +use network::{ProtocolAddr, MessageBuffer, Network, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; use serde::{Deserialize, Serialize}; use std::{ sync::Arc, @@ -96,9 +96,9 @@ fn main() { let port: u16 = matches.value_of("port").unwrap().parse().unwrap(); let ip: &str = matches.value_of("ip").unwrap(); let address = match matches.value_of("protocol") { - Some("tcp") => Address::Tcp(format!("{}:{}", ip, port).parse().unwrap()), - Some("udp") => Address::Udp(format!("{}:{}", ip, port).parse().unwrap()), - _ => panic!("invalid mode, run --help!"), + Some("tcp") => ProtocolAddr::Tcp(format!("{}:{}", ip, port).parse().unwrap()), + Some("udp") => ProtocolAddr::Udp(format!("{}:{}", ip, port).parse().unwrap()), + _ => panic!("Invalid mode, run --help!"), }; let mut background = None; @@ -111,22 +111,22 @@ fn main() { thread::sleep(Duration::from_millis(200)); //start client after server client(address); }, - _ => panic!("invalid mode, run --help!"), + _ => panic!("Invalid mode, run --help!"), }; if let Some(background) = background { background.join().unwrap(); } } -fn server(address: Address) { +fn server(address: ProtocolAddr) { let mut metrics = metrics::SimpleMetrics::new(); - let (server, f) = Network::new(Pid::new(), Some(metrics.registry())); + let (server, f) = Network::new_with_registry(Pid::new(), metrics.registry()); std::thread::spawn(f); metrics.run("0.0.0.0:59112".parse().unwrap()).unwrap(); block_on(server.listen(address)).unwrap(); loop { - info!("waiting for participant to connect"); + info!("Waiting for participant to connect"); let p1 = block_on(server.connected()).unwrap(); //remote representation of p1 let mut s1 = block_on(p1.opened()).unwrap(); //remote representation of s1 block_on(async { @@ -138,17 +138,17 @@ fn server(address: Address) { let new = Instant::now(); let diff = new.duration_since(last); last = new; - println!("recv 1.000.000 took {}", diff.as_millis()); + println!("Recv 1.000.000 took {}", diff.as_millis()); } } - info!("other stream was closed"); + info!("Other stream was closed"); }); } } -fn client(address: Address) { +fn client(address: ProtocolAddr) { let mut metrics = metrics::SimpleMetrics::new(); - let (client, f) = Network::new(Pid::new(), Some(metrics.registry())); + let (client, f) = Network::new_with_registry(Pid::new(), metrics.registry()); std::thread::spawn(f); metrics.run("0.0.0.0:59111".parse().unwrap()).unwrap(); @@ -170,18 +170,18 @@ fn client(address: Address) { let new = Instant::now(); let diff = new.duration_since(last); last = new; - println!("send 1.000.000 took {}", diff.as_millis()); + println!("Send 1.000.000 took {}", diff.as_millis()); } if id > 2000000 { - println!("stop"); + println!("Stop"); std::thread::sleep(std::time::Duration::from_millis(5000)); break; } } drop(s1); std::thread::sleep(std::time::Duration::from_millis(5000)); - info!("closing participant"); - block_on(client.disconnect(p1)).unwrap(); + info!("Closing participant"); + block_on(p1.disconnect()).unwrap(); std::thread::sleep(std::time::Duration::from_millis(25000)); info!("DROPPING! client"); drop(client); diff --git a/network/examples/network-speed/src/metrics.rs b/network/examples/network-speed/src/metrics.rs index 978c686d58..4052c606a9 100644 --- a/network/examples/network-speed/src/metrics.rs +++ b/network/examples/network-speed/src/metrics.rs @@ -55,7 +55,7 @@ impl SimpleMetrics { Ok(Some(rq)) => rq, Ok(None) => continue, Err(e) => { - println!("error: {}", e); + println!("Error: {}", e); break; }, }; @@ -76,7 +76,7 @@ impl SimpleMetrics { _ => (), } } - debug!("stopping tiny_http server to serve metrics"); + debug!("Stopping tiny_http server to serve metrics"); })); Ok(()) } diff --git a/network/src/api.rs b/network/src/api.rs index 110bae0fa6..54de28cf35 100644 --- a/network/src/api.rs +++ b/network/src/api.rs @@ -4,15 +4,21 @@ //! (cd network/examples/async_recv && RUST_BACKTRACE=1 cargo run) use crate::{ message::{self, partial_eq_bincode, IncomingMessage, MessageBuffer, OutgoingMessage}, + participant::{A2bStreamOpen, S2bShutdownBparticipant}, scheduler::Scheduler, types::{Mid, Pid, Prio, Promises, Sid}, }; -use async_std::{io, sync::RwLock, task}; +use async_std::{ + io, + sync::{Mutex, RwLock}, + task, +}; use futures::{ channel::{mpsc, oneshot}, sink::SinkExt, stream::StreamExt, }; +#[cfg(feature = "metrics")] use prometheus::Registry; use serde::{de::DeserializeOwned, Serialize}; use std::{ @@ -26,9 +32,11 @@ use std::{ use tracing::*; use tracing_futures::Instrument; +type A2sDisconnect = Arc>>>; + /// Represents a Tcp or Udp or Mpsc address #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub enum Address { +pub enum ProtocolAddr { Tcp(SocketAddr), Udp(SocketAddr), Mpsc(u64), @@ -44,11 +52,9 @@ pub enum Address { pub struct Participant { local_pid: Pid, remote_pid: Pid, - a2b_steam_open_s: RwLock)>>, + a2b_stream_open_s: RwLock>, b2a_stream_opened_r: RwLock>, - closed: AtomicBool, - a2s_disconnect_s: - Option>)>>, + a2s_disconnect_s: A2sDisconnect, } /// `Streams` represents a channel to send `n` messages with a certain priority @@ -71,9 +77,9 @@ pub struct Stream { mid: Mid, prio: Prio, promises: Promises, + send_closed: Arc, a2b_msg_s: crossbeam_channel::Sender<(Prio, Sid, OutgoingMessage)>, b2a_msg_recv_r: mpsc::UnboundedReceiver, - closed: Arc, a2b_close_stream_s: Option>, } @@ -83,13 +89,18 @@ pub enum NetworkError { NetworkClosed, ListenFailed(std::io::Error), ConnectFailed(std::io::Error), - GracefulDisconnectFailed(std::io::Error), } /// Error type thrown by [`Participants`](Participant) methods -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum ParticipantError { - ParticipantClosed, + ///Participant was closed by remote side + ParticipantDisconnected, + ///Underlying Protocol failed and wasn't able to recover, expect some Data + /// loss unfortunately, there is no method to get the exact messages + /// that failed. This is also returned when local side tries to do + /// something while remote site gracefully disconnects + ProtocolFailedUnrecoverable, } /// Error type thrown by [`Streams`](Stream) methods @@ -105,27 +116,26 @@ pub enum StreamError { /// Application. You can pass it around multiple threads in an /// [`Arc`](std::sync::Arc) as all commands have internal mutability. /// -/// The `Network` has methods to [`connect`] and [`disconnect`] to other -/// [`Participants`] via their [`Address`]. All [`Participants`] will be stored -/// in the Network until explicitly disconnected, which is the only way to close -/// the sockets. +/// The `Network` has methods to [`connect`] to other [`Participants`] actively +/// via their [`ProtocolAddr`], or [`listen`] passively for [`connected`] +/// [`Participants`]. /// /// # Examples /// ```rust -/// use veloren_network::{Network, Address, Pid}; +/// use veloren_network::{Network, ProtocolAddr, Pid}; /// use futures::executor::block_on; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, listen on port `2999` to accept connections and connect to port `8080` to connect to a (pseudo) database Application -/// let (network, f) = Network::new(Pid::new(), None); +/// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); /// block_on(async{ /// # //setup pseudo database! -/// # let (database, fd) = Network::new(Pid::new(), None); +/// # let (database, fd) = Network::new(Pid::new()); /// # std::thread::spawn(fd); -/// # database.listen(Address::Tcp("127.0.0.1:8080".parse().unwrap())).await?; -/// network.listen(Address::Tcp("127.0.0.1:2999".parse().unwrap())).await?; -/// let database = network.connect(Address::Tcp("127.0.0.1:8080".parse().unwrap())).await?; +/// # database.listen(ProtocolAddr::Tcp("127.0.0.1:8080".parse().unwrap())).await?; +/// network.listen(ProtocolAddr::Tcp("127.0.0.1:2999".parse().unwrap())).await?; +/// let database = network.connect(ProtocolAddr::Tcp("127.0.0.1:8080".parse().unwrap())).await?; /// # Ok(()) /// }) /// # } @@ -133,14 +143,15 @@ pub enum StreamError { /// /// [`Participants`]: crate::api::Participant /// [`connect`]: Network::connect -/// [`disconnect`]: Network::disconnect +/// [`listen`]: Network::listen +/// [`connected`]: Network::connected pub struct Network { local_pid: Pid, - participants: RwLock>>, + participant_disconnect_sender: RwLock>, listen_sender: - RwLock>)>>, + RwLock>)>>, connect_sender: - RwLock>)>>, + RwLock>)>>, connected_receiver: RwLock>, shutdown_sender: Option>, } @@ -151,9 +162,6 @@ impl Network { /// # Arguments /// * `participant_id` - provide it by calling [`Pid::new()`], usually you /// don't want to reuse a Pid for 2 `Networks` - /// * `registry` - Provide a Registy in order to collect Prometheus metrics - /// by this `Network`, `None` will deactivate Tracing. Tracing is done via - /// [`prometheus`] /// /// # Result /// * `Self` - returns a `Network` which can be `Send` to multiple areas of @@ -170,22 +178,22 @@ impl Network { /// ```rust /// //Example with uvth /// use uvth::ThreadPoolBuilder; - /// use veloren_network::{Address, Network, Pid}; + /// use veloren_network::{Network, Pid, ProtocolAddr}; /// /// let pool = ThreadPoolBuilder::new().build(); - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// pool.execute(f); /// ``` /// /// ```rust /// //Example with std::thread - /// use veloren_network::{Address, Network, Pid}; + /// use veloren_network::{Network, Pid, ProtocolAddr}; /// - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); /// ``` /// - /// Usually you only create a single `Network` for an appliregistrycation, + /// Usually you only create a single `Network` for an application, /// except when client and server are in the same application, then you /// will want 2. However there are no technical limitations from /// creating more. @@ -193,36 +201,73 @@ impl Network { /// [`Pid::new()`]: crate::types::Pid::new /// [`ThreadPool`]: https://docs.rs/uvth/newest/uvth/struct.ThreadPool.html /// [`uvth`]: https://docs.rs/uvth - pub fn new( + pub fn new(participant_id: Pid) -> (Self, impl std::ops::FnOnce()) { + Self::internal_new( + participant_id, + #[cfg(feature = "metrics")] + None, + ) + } + + /// See [`new`] + /// + /// # additional Arguments + /// * `registry` - Provide a Registy in order to collect Prometheus metrics + /// by this `Network`, `None` will deactivate Tracing. Tracing is done via + /// [`prometheus`] + /// + /// # Examples + /// ```rust + /// use prometheus::Registry; + /// use veloren_network::{Network, Pid, ProtocolAddr}; + /// + /// let registry = Registry::new(); + /// let (network, f) = Network::new_with_registry(Pid::new(), ®istry); + /// std::thread::spawn(f); + /// ``` + /// [`new`]: crate::api::Network::new + #[cfg(feature = "metrics")] + pub fn new_with_registry( participant_id: Pid, - registry: Option<&Registry>, + registry: &Registry, + ) -> (Self, impl std::ops::FnOnce()) { + Self::internal_new(participant_id, Some(registry)) + } + + fn internal_new( + participant_id: Pid, + #[cfg(feature = "metrics")] registry: Option<&Registry>, ) -> (Self, impl std::ops::FnOnce()) { let p = participant_id; - debug!(?p, "starting Network"); + debug!(?p, "Starting Network"); let (scheduler, listen_sender, connect_sender, connected_receiver, shutdown_sender) = - Scheduler::new(participant_id, registry); + Scheduler::new( + participant_id, + #[cfg(feature = "metrics")] + registry, + ); ( Self { local_pid: participant_id, - participants: RwLock::new(HashMap::new()), + participant_disconnect_sender: RwLock::new(HashMap::new()), listen_sender: RwLock::new(listen_sender), connect_sender: RwLock::new(connect_sender), connected_receiver: RwLock::new(connected_receiver), shutdown_sender: Some(shutdown_sender), }, move || { - trace!(?p, "starting sheduler in own thread"); + trace!(?p, "Starting scheduler in own thread"); let _handle = task::block_on( scheduler .run() .instrument(tracing::info_span!("scheduler", ?p)), ); - trace!(?p, "stopping sheduler and his own thread"); + trace!(?p, "Stopping scheduler and his own thread"); }, ) } - /// starts listening on an [`Address`]. + /// starts listening on an [`ProtocolAddr`]. /// When the method returns the `Network` is ready to listen for incoming /// connections OR has returned a [`NetworkError`] (e.g. port already used). /// You can call [`connected`] to asynchrony wait for a [`Participant`] to @@ -232,18 +277,18 @@ impl Network { /// # Examples /// ```rust /// use futures::executor::block_on; - /// use veloren_network::{Address, Network, Pid}; + /// use veloren_network::{Network, Pid, ProtocolAddr}; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, listen on port `2000` TCP on all NICs and `2001` UDP locally - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); /// block_on(async { /// network - /// .listen(Address::Tcp("0.0.0.0:2000".parse().unwrap())) + /// .listen(ProtocolAddr::Tcp("0.0.0.0:2000".parse().unwrap())) /// .await?; /// network - /// .listen(Address::Udp("127.0.0.1:2001".parse().unwrap())) + /// .listen(ProtocolAddr::Udp("127.0.0.1:2001".parse().unwrap())) /// .await?; /// # Ok(()) /// }) @@ -251,7 +296,7 @@ impl Network { /// ``` /// /// [`connected`]: Network::connected - pub async fn listen(&self, address: Address) -> Result<(), NetworkError> { + pub async fn listen(&self, address: ProtocolAddr) -> Result<(), NetworkError> { let (s2a_result_s, s2a_result_r) = oneshot::channel::>(); debug!(?address, "listening on address"); self.listen_sender @@ -267,49 +312,49 @@ impl Network { } } - /// starts connectiong to an [`Address`]. + /// starts connectiong to an [`ProtocolAddr`]. /// When the method returns the Network either returns a [`Participant`] /// ready to open [`Streams`] on OR has returned a [`NetworkError`] (e.g. /// can't connect, or invalid Handshake) # Examples /// ```rust /// use futures::executor::block_on; - /// use veloren_network::{Address, Network, Pid}; + /// use veloren_network::{Network, Pid, ProtocolAddr}; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, connect on port `2010` TCP and `2011` UDP like listening above - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); + /// # let (remote, fr) = Network::new(Pid::new()); /// # std::thread::spawn(fr); /// block_on(async { - /// # remote.listen(Address::Tcp("0.0.0.0:2010".parse().unwrap())).await?; - /// # remote.listen(Address::Udp("0.0.0.0:2011".parse().unwrap())).await?; + /// # remote.listen(ProtocolAddr::Tcp("0.0.0.0:2010".parse().unwrap())).await?; + /// # remote.listen(ProtocolAddr::Udp("0.0.0.0:2011".parse().unwrap())).await?; /// let p1 = network - /// .connect(Address::Tcp("127.0.0.1:2010".parse().unwrap())) + /// .connect(ProtocolAddr::Tcp("127.0.0.1:2010".parse().unwrap())) /// .await?; /// # //this doesn't work yet, so skip the test /// # //TODO fixme! /// # return Ok(()); /// let p2 = network - /// .connect(Address::Udp("127.0.0.1:2011".parse().unwrap())) + /// .connect(ProtocolAddr::Udp("127.0.0.1:2011".parse().unwrap())) /// .await?; - /// assert!(std::sync::Arc::ptr_eq(&p1, &p2)); + /// assert_eq!(&p1, &p2); /// # Ok(()) /// }) /// # } /// ``` /// Usually the `Network` guarantees that a operation on a [`Participant`] /// succeeds, e.g. by automatic retrying unless it fails completely e.g. by - /// disconnecting from the remote. If 2 [`Addresses`] you `connect` to + /// disconnecting from the remote. If 2 [`ProtocolAddres`] you `connect` to /// belongs to the same [`Participant`], you get the same [`Participant`] as /// a result. This is useful e.g. by connecting to the same /// [`Participant`] via multiple Protocols. /// /// [`Streams`]: crate::api::Stream - /// [`Addresses`]: crate::api::Address - pub async fn connect(&self, address: Address) -> Result, NetworkError> { + /// [`ProtocolAddres`]: crate::api::ProtocolAddr + pub async fn connect(&self, address: ProtocolAddr) -> Result { let (pid_sender, pid_receiver) = oneshot::channel::>(); - debug!(?address, "connect to address"); + debug!(?address, "Connect to address"); self.connect_sender .write() .await @@ -322,17 +367,16 @@ impl Network { let pid = participant.remote_pid; debug!( ?pid, - "received Participant id from remote and return to user" + "Received Participant id from remote and return to user" ); - let participant = Arc::new(participant); - self.participants + self.participant_disconnect_sender .write() .await - .insert(participant.remote_pid, participant.clone()); + .insert(pid, participant.a2s_disconnect_s.clone()); Ok(participant) } - /// returns a [`Participant`] created from a [`Address`] you called + /// returns a [`Participant`] created from a [`ProtocolAddr`] you called /// [`listen`] on before. This function will either return a working /// [`Participant`] ready to open [`Streams`] on OR has returned a /// [`NetworkError`] (e.g. Network got closed) @@ -340,19 +384,19 @@ impl Network { /// # Examples /// ```rust /// use futures::executor::block_on; - /// use veloren_network::{Address, Network, Pid}; + /// use veloren_network::{Network, Pid, ProtocolAddr}; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, listen on port `2020` TCP and opens returns their Pid - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); + /// # let (remote, fr) = Network::new(Pid::new()); /// # std::thread::spawn(fr); /// block_on(async { /// network - /// .listen(Address::Tcp("0.0.0.0:2020".parse().unwrap())) + /// .listen(ProtocolAddr::Tcp("0.0.0.0:2020".parse().unwrap())) /// .await?; - /// # remote.connect(Address::Tcp("0.0.0.0:2020".parse().unwrap())).await?; + /// # remote.connect(ProtocolAddr::Tcp("0.0.0.0:2020".parse().unwrap())).await?; /// while let Ok(participant) = network.connected().await { /// println!("Participant connected: {}", participant.remote_pid()); /// # //skip test here as it would be a endless loop @@ -365,146 +409,30 @@ impl Network { /// /// [`Streams`]: crate::api::Stream /// [`listen`]: crate::api::Network::listen - pub async fn connected(&self) -> Result, NetworkError> { + pub async fn connected(&self) -> Result { let participant = self.connected_receiver.write().await.next().await?; - let participant = Arc::new(participant); - self.participants + self.participant_disconnect_sender .write() .await - .insert(participant.remote_pid, participant.clone()); + .insert(participant.remote_pid, participant.a2s_disconnect_s.clone()); Ok(participant) } - - /// disconnecting a [`Participant`] where you move the last existing - /// [`Arc`]. As the [`Network`] also holds [`Arc`] to the - /// [`Participant`], you need to provide the last [`Arc`] and - /// are not allowed to keep others. If you do so the [`Participant`] - /// can't be disconnected properly. If you no longer have the respective - /// [`Participant`], try using the [`participants`] method to get it. - /// - /// This function will wait for all [`Streams`] to properly close, including - /// all messages to be send before closing. If an error occurs with one - /// of the messages. - /// Except if the remote side already dropped the [`Participant`] - /// simultaneously, then messages won't be sended - /// - /// There is NO `disconnected` function in `Network`, if a [`Participant`] - /// is no longer reachable (e.g. as the network cable was unplugged) the - /// [`Participant`] will fail all action, but needs to be manually - /// disconected, using this function. - /// - /// # Examples - /// ```rust - /// use futures::executor::block_on; - /// use veloren_network::{Address, Network, Pid}; - /// - /// # fn main() -> std::result::Result<(), Box> { - /// // Create a Network, listen on port `2030` TCP and opens returns their Pid and close connection. - /// let (network, f) = Network::new(Pid::new(), None); - /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); - /// # std::thread::spawn(fr); - /// block_on(async { - /// network - /// .listen(Address::Tcp("0.0.0.0:2030".parse().unwrap())) - /// .await?; - /// # remote.connect(Address::Tcp("0.0.0.0:2030".parse().unwrap())).await?; - /// while let Ok(participant) = network.connected().await { - /// println!("Participant connected: {}", participant.remote_pid()); - /// network.disconnect(participant).await?; - /// # //skip test here as it would be a endless loop - /// # break; - /// } - /// # Ok(()) - /// }) - /// # } - /// ``` - /// - /// [`Arc`]: crate::api::Participant - /// [`Streams`]: crate::api::Stream - /// [`participants`]: Network::participants - /// [`Arc`]: std::sync::Arc - pub async fn disconnect(&self, participant: Arc) -> Result<(), NetworkError> { - // Remove, Close and try_unwrap error when unwrap fails! - let pid = participant.remote_pid; - debug!(?pid, "removing participant from network"); - self.participants.write().await.remove(&pid)?; - participant.closed.store(true, Ordering::Relaxed); - - match Arc::try_unwrap(participant) { - Err(_) => { - warn!( - "you are disconnecting and still keeping a reference to this participant, \ - this is a bad idea. Participant will only be dropped when you drop your last \ - reference" - ); - Ok(()) - }, - Ok(mut participant) => { - trace!("waiting now for participant to close"); - let (finished_sender, finished_receiver) = oneshot::channel(); - // we are deleting here asyncly before DROP is called. Because this is done - // nativly async, while drop needs an BLOCK! Drop will recognis - // that it has been delete here and don't try another double delete. - participant - .a2s_disconnect_s - .take() - .unwrap() - .send((pid, finished_sender)) - .await - .expect("something is wrong in internal scheduler coding"); - match finished_receiver.await { - Ok(Ok(())) => { - trace!(?pid, "Participant is now closed"); - Ok(()) - }, - Ok(Err(e)) => { - trace!( - ?e, - "Error occured during shutdown of participant and is propagated to \ - User" - ); - Err(NetworkError::GracefulDisconnectFailed(e)) - }, - Err(e) => { - error!( - ?pid, - ?e, - "Failed to get a message back from the scheduler, closing the network" - ); - Err(NetworkError::NetworkClosed) - }, - } - }, - } - } - - /// returns a copy of all current connected [`Participants`], - /// including ones, which can't send data anymore as the underlying sockets - /// are closed already but haven't been [`disconnected`] yet. - /// - /// [`Participants`]: crate::api::Participant - /// [`disconnected`]: Network::disconnect - pub async fn participants(&self) -> HashMap> { - self.participants.read().await.clone() - } } impl Participant { pub(crate) fn new( local_pid: Pid, remote_pid: Pid, - a2b_steam_open_s: mpsc::UnboundedSender<(Prio, Promises, oneshot::Sender)>, + a2b_stream_open_s: mpsc::UnboundedSender, b2a_stream_opened_r: mpsc::UnboundedReceiver, - a2s_disconnect_s: mpsc::UnboundedSender<(Pid, oneshot::Sender>)>, + a2s_disconnect_s: mpsc::UnboundedSender<(Pid, S2bShutdownBparticipant)>, ) -> Self { Self { local_pid, remote_pid, - a2b_steam_open_s: RwLock::new(a2b_steam_open_s), + a2b_stream_open_s: RwLock::new(a2b_stream_open_s), b2a_stream_opened_r: RwLock::new(b2a_stream_opened_r), - closed: AtomicBool::new(false), - a2s_disconnect_s: Some(a2s_disconnect_s), + a2s_disconnect_s: Arc::new(Mutex::new(Some(a2s_disconnect_s))), } } @@ -528,18 +456,18 @@ impl Participant { /// # Examples /// ```rust /// use futures::executor::block_on; - /// use veloren_network::{Address, Network, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; + /// use veloren_network::{Network, Pid, ProtocolAddr, PROMISES_CONSISTENCY, PROMISES_ORDERED}; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, connect on port 2100 and open a stream - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); + /// # let (remote, fr) = Network::new(Pid::new()); /// # std::thread::spawn(fr); /// block_on(async { - /// # remote.listen(Address::Tcp("0.0.0.0:2100".parse().unwrap())).await?; + /// # remote.listen(ProtocolAddr::Tcp("0.0.0.0:2100".parse().unwrap())).await?; /// let p1 = network - /// .connect(Address::Tcp("127.0.0.1:2100".parse().unwrap())) + /// .connect(ProtocolAddr::Tcp("127.0.0.1:2100".parse().unwrap())) /// .await?; /// let _s1 = p1.open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY).await?; /// # Ok(()) @@ -551,20 +479,14 @@ impl Participant { pub async fn open(&self, prio: u8, promises: Promises) -> Result { //use this lock for now to make sure that only one open at a time is made, // TODO: not sure if we can paralise that, check in future - let mut a2b_steam_open_s = self.a2b_steam_open_s.write().await; - if self.closed.load(Ordering::Relaxed) { - warn!(?self.remote_pid, "participant is closed but another open is tried on it"); - return Err(ParticipantError::ParticipantClosed); - } + let mut a2b_stream_open_s = self.a2b_stream_open_s.write().await; let (p2a_return_stream_s, p2a_return_stream_r) = oneshot::channel(); - if a2b_steam_open_s + if let Err(e) = a2b_stream_open_s .send((prio, promises, p2a_return_stream_s)) .await - .is_err() { - debug!(?self.remote_pid, "stream_open_sender failed, closing participant"); - self.closed.store(true, Ordering::Relaxed); - return Err(ParticipantError::ParticipantClosed); + debug!(?e, "bParticipant is already closed, notifying"); + return Err(ParticipantError::ParticipantDisconnected); } match p2a_return_stream_r.await { Ok(stream) => { @@ -574,8 +496,7 @@ impl Participant { }, Err(_) => { debug!(?self.remote_pid, "p2a_return_stream_r failed, closing participant"); - self.closed.store(true, Ordering::Relaxed); - Err(ParticipantError::ParticipantClosed) + Err(ParticipantError::ParticipantDisconnected) }, } } @@ -589,19 +510,19 @@ impl Participant { /// /// # Examples /// ```rust - /// use veloren_network::{Network, Pid, Address, PROMISES_ORDERED, PROMISES_CONSISTENCY}; + /// use veloren_network::{Network, Pid, ProtocolAddr, PROMISES_ORDERED, PROMISES_CONSISTENCY}; /// use futures::executor::block_on; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, connect on port 2110 and wait for the other side to open a stream /// // Note: It's quite unusal to activly connect, but then wait on a stream to be connected, usually the Appication taking initiative want's to also create the first Stream. - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); + /// # let (remote, fr) = Network::new(Pid::new()); /// # std::thread::spawn(fr); /// block_on(async { - /// # remote.listen(Address::Tcp("0.0.0.0:2110".parse().unwrap())).await?; - /// let p1 = network.connect(Address::Tcp("127.0.0.1:2110".parse().unwrap())).await?; + /// # remote.listen(ProtocolAddr::Tcp("0.0.0.0:2110".parse().unwrap())).await?; + /// let p1 = network.connect(ProtocolAddr::Tcp("127.0.0.1:2110".parse().unwrap())).await?; /// # let p2 = remote.connected().await?; /// # p2.open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY).await?; /// let _s1 = p1.opened().await?; @@ -617,20 +538,105 @@ impl Participant { //use this lock for now to make sure that only one open at a time is made, // TODO: not sure if we can paralise that, check in future let mut stream_opened_receiver = self.b2a_stream_opened_r.write().await; - if self.closed.load(Ordering::Relaxed) { - warn!(?self.remote_pid, "participant is closed but another open is tried on it"); - return Err(ParticipantError::ParticipantClosed); - } match stream_opened_receiver.next().await { Some(stream) => { let sid = stream.sid; - debug!(?sid, ?self.remote_pid, "receive opened stream"); + debug!(?sid, ?self.remote_pid, "Receive opened stream"); Ok(stream) }, None => { debug!(?self.remote_pid, "stream_opened_receiver failed, closing participant"); - self.closed.store(true, Ordering::Relaxed); - Err(ParticipantError::ParticipantClosed) + Err(ParticipantError::ParticipantDisconnected) + }, + } + } + + /// disconnecting a `Participant` in a async way. + /// Use this rather than `Participant::Drop` if you want to close multiple + /// `Participants`. + /// + /// This function will wait for all [`Streams`] to properly close, including + /// all messages to be send before closing. If an error occurs with one + /// of the messages. + /// Except if the remote side already dropped the `Participant` + /// simultaneously, then messages won't be send + /// + /// There is NO `disconnected` function in `Participant`, if a `Participant` + /// is no longer reachable (e.g. as the network cable was unplugged) the + /// `Participant` will fail all action, but needs to be manually + /// disconected, using this function. + /// + /// # Examples + /// ```rust + /// use futures::executor::block_on; + /// use veloren_network::{Network, Pid, ProtocolAddr}; + /// + /// # fn main() -> std::result::Result<(), Box> { + /// // Create a Network, listen on port `2030` TCP and opens returns their Pid and close connection. + /// let (network, f) = Network::new(Pid::new()); + /// std::thread::spawn(f); + /// # let (remote, fr) = Network::new(Pid::new()); + /// # std::thread::spawn(fr); + /// block_on(async { + /// network + /// .listen(ProtocolAddr::Tcp("0.0.0.0:2030".parse().unwrap())) + /// .await?; + /// # let keep_alive = remote.connect(ProtocolAddr::Tcp("0.0.0.0:2030".parse().unwrap())).await?; + /// while let Ok(participant) = network.connected().await { + /// println!("Participant connected: {}", participant.remote_pid()); + /// participant.disconnect().await?; + /// # //skip test here as it would be a endless loop + /// # break; + /// } + /// # Ok(()) + /// }) + /// # } + /// ``` + /// + /// [`Streams`]: crate::api::Stream + pub async fn disconnect(self) -> Result<(), ParticipantError> { + // Remove, Close and try_unwrap error when unwrap fails! + let pid = self.remote_pid; + debug!(?pid, "Closing participant from network"); + + //Streams will be closed by BParticipant + match self.a2s_disconnect_s.lock().await.take() { + Some(mut a2s_disconnect_s) => { + let (finished_sender, finished_receiver) = oneshot::channel(); + // Participant is connecting to Scheduler here, not as usual + // Participant<->BParticipant + a2s_disconnect_s + .send((pid, finished_sender)) + .await + .expect("Something is wrong in internal scheduler coding"); + match finished_receiver.await { + Ok(res) => { + match res { + Ok(()) => trace!(?pid, "Participant is now closed"), + Err(ref e) => { + trace!(?pid, ?e, "Error occured during shutdown of participant") + }, + }; + res + }, + Err(e) => { + //this is a bug. but as i am Participant i can't destroy the network + error!( + ?pid, + ?e, + "Failed to get a message back from the scheduler, seems like the \ + network is already closed" + ); + Err(ParticipantError::ProtocolFailedUnrecoverable) + }, + } + }, + None => { + warn!( + "seems like you are trying to disconnecting a participant after the network \ + was already dropped. It was already dropped with the network!" + ); + Err(ParticipantError::ParticipantDisconnected) }, } } @@ -646,9 +652,9 @@ impl Stream { sid: Sid, prio: Prio, promises: Promises, + send_closed: Arc, a2b_msg_s: crossbeam_channel::Sender<(Prio, Sid, OutgoingMessage)>, b2a_msg_recv_r: mpsc::UnboundedReceiver, - closed: Arc, a2b_close_stream_s: mpsc::UnboundedSender, ) -> Self { Self { @@ -657,9 +663,9 @@ impl Stream { mid: 0, prio, promises, + send_closed, a2b_msg_s, b2a_msg_recv_r, - closed, a2b_close_stream_s: Some(a2b_close_stream_s), } } @@ -690,19 +696,19 @@ impl Stream { /// /// # Example /// ``` - /// use veloren_network::{Network, Address, Pid}; + /// use veloren_network::{Network, ProtocolAddr, Pid}; /// # use veloren_network::{PROMISES_ORDERED, PROMISES_CONSISTENCY}; /// use futures::executor::block_on; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, listen on Port `2200` and wait for a Stream to be opened, then answer `Hello World` - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); + /// # let (remote, fr) = Network::new(Pid::new()); /// # std::thread::spawn(fr); /// block_on(async { - /// network.listen(Address::Tcp("127.0.0.1:2200".parse().unwrap())).await?; - /// # let remote_p = remote.connect(Address::Tcp("127.0.0.1:2200".parse().unwrap())).await?; + /// network.listen(ProtocolAddr::Tcp("127.0.0.1:2200".parse().unwrap())).await?; + /// # let remote_p = remote.connect(ProtocolAddr::Tcp("127.0.0.1:2200".parse().unwrap())).await?; /// # // keep it alive /// # let _stream_p = remote_p.open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY).await?; /// let participant_a = network.connected().await?; @@ -729,23 +735,23 @@ impl Stream { /// /// # Example /// ```rust - /// use veloren_network::{Network, Address, Pid, MessageBuffer}; + /// use veloren_network::{Network, ProtocolAddr, Pid, MessageBuffer}; /// # use veloren_network::{PROMISES_ORDERED, PROMISES_CONSISTENCY}; /// use futures::executor::block_on; /// use bincode; /// use std::sync::Arc; /// /// # fn main() -> std::result::Result<(), Box> { - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote1, fr1) = Network::new(Pid::new(), None); + /// # let (remote1, fr1) = Network::new(Pid::new()); /// # std::thread::spawn(fr1); - /// # let (remote2, fr2) = Network::new(Pid::new(), None); + /// # let (remote2, fr2) = Network::new(Pid::new()); /// # std::thread::spawn(fr2); /// block_on(async { - /// network.listen(Address::Tcp("127.0.0.1:2210".parse().unwrap())).await?; - /// # let remote1_p = remote1.connect(Address::Tcp("127.0.0.1:2210".parse().unwrap())).await?; - /// # let remote2_p = remote2.connect(Address::Tcp("127.0.0.1:2210".parse().unwrap())).await?; + /// network.listen(ProtocolAddr::Tcp("127.0.0.1:2210".parse().unwrap())).await?; + /// # let remote1_p = remote1.connect(ProtocolAddr::Tcp("127.0.0.1:2210".parse().unwrap())).await?; + /// # let remote2_p = remote2.connect(ProtocolAddr::Tcp("127.0.0.1:2210".parse().unwrap())).await?; /// # assert_eq!(remote1_p.remote_pid(), remote2_p.remote_pid()); /// # remote1_p.open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY).await?; /// # remote2_p.open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY).await?; @@ -770,10 +776,9 @@ impl Stream { /// [`send`]: Stream::send /// [`Participants`]: crate::api::Participant pub fn send_raw(&mut self, messagebuffer: Arc) -> Result<(), StreamError> { - if self.closed.load(Ordering::Relaxed) { + if self.send_closed.load(Ordering::Relaxed) { return Err(StreamError::StreamClosed); } - //debug!(?messagebuffer, "sending a message"); self.a2b_msg_s.send((self.prio, self.sid, OutgoingMessage { buffer: messagebuffer, cursor: 0, @@ -795,19 +800,19 @@ impl Stream { /// /// # Example /// ``` - /// use veloren_network::{Network, Address, Pid}; + /// use veloren_network::{Network, ProtocolAddr, Pid}; /// # use veloren_network::{PROMISES_ORDERED, PROMISES_CONSISTENCY}; /// use futures::executor::block_on; /// /// # fn main() -> std::result::Result<(), Box> { /// // Create a Network, listen on Port `2220` and wait for a Stream to be opened, then listen on it - /// let (network, f) = Network::new(Pid::new(), None); + /// let (network, f) = Network::new(Pid::new()); /// std::thread::spawn(f); - /// # let (remote, fr) = Network::new(Pid::new(), None); + /// # let (remote, fr) = Network::new(Pid::new()); /// # std::thread::spawn(fr); /// block_on(async { - /// network.listen(Address::Tcp("127.0.0.1:2220".parse().unwrap())).await?; - /// # let remote_p = remote.connect(Address::Tcp("127.0.0.1:2220".parse().unwrap())).await?; + /// network.listen(ProtocolAddr::Tcp("127.0.0.1:2220".parse().unwrap())).await?; + /// # let remote_p = remote.connect(ProtocolAddr::Tcp("127.0.0.1:2220".parse().unwrap())).await?; /// # let mut stream_p = remote_p.open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY).await?; /// # stream_p.send("Hello World"); /// let participant_a = network.connected().await?; @@ -829,46 +834,72 @@ impl Stream { /// [`send_raw`]: Stream::send_raw /// [`recv`]: Stream::recv pub async fn recv_raw(&mut self) -> Result { - //no need to access self.closed here, as when this stream is closed the Channel - // is closed which will trigger a None let msg = self.b2a_msg_recv_r.next().await?; - //info!(?msg, "delivering a message"); Ok(msg.buffer) } } +/// +impl core::cmp::PartialEq for Participant { + fn eq(&self, other: &Self) -> bool { + //don't check local_pid, 2 Participant from different network should match if + // they are the "same" + self.remote_pid == other.remote_pid + } +} + impl Drop for Network { fn drop(&mut self) { let pid = self.local_pid; - debug!(?pid, "shutting down Network"); - debug!( + debug!(?pid, "Shutting down Network"); + trace!( ?pid, - "shutting down Participants of Network, while we still have metrics" + "Shutting down Participants of Network, while we still have metrics" ); + let mut finished_receiver_list = vec![]; task::block_on(async { - // we need to carefully shut down here! as otherwise we might call - // Participant::Drop with a2s_disconnect_s here which would open - // another task::block, which would panic! also i can't `.write` on - // `self.participants` as the `disconnect` fn needs it. - let mut participant_clone = self.participants().await; - for (_, p) in participant_clone.drain() { - if let Err(e) = self.disconnect(p).await { - error!( - ?e, - "error while dropping network, the error occured when dropping a \ - participant but can't be notified to the user any more" - ); + // we MUST avoid nested block_on, good that Network::Drop no longer triggers + // Participant::Drop directly but just the BParticipant + for (remote_pid, a2s_disconnect_s) in + self.participant_disconnect_sender.write().await.drain() + { + match a2s_disconnect_s.lock().await.take() { + Some(mut a2s_disconnect_s) => { + trace!(?remote_pid, "Participants will be closed"); + let (finished_sender, finished_receiver) = oneshot::channel(); + finished_receiver_list.push((remote_pid, finished_receiver)); + a2s_disconnect_s + .send((remote_pid, finished_sender)) + .await + .expect( + "Scheduler is closed, but nobody other should be able to close it", + ); + }, + None => trace!(?remote_pid, "Participant already disconnected gracefully"), + } + } + //wait after close is requested for all + for (remote_pid, finished_receiver) in finished_receiver_list.drain(..) { + match finished_receiver.await { + Ok(Ok(())) => trace!(?remote_pid, "disconnect successful"), + Ok(Err(e)) => info!(?remote_pid, ?e, "unclean disconnect"), + Err(e) => warn!( + ?remote_pid, + ?e, + "Failed to get a message back from the scheduler, seems like the network \ + is already closed" + ), } } - self.participants.write().await.clear(); }); - debug!(?pid, "shutting down Scheduler"); + trace!(?pid, "Participants have shut down!"); + trace!(?pid, "Shutting down Scheduler"); self.shutdown_sender .take() .unwrap() .send(()) - .expect("scheduler is closed, but nobody other should be able to close it"); - debug!(?pid, "participants have shut down!"); + .expect("Scheduler is closed, but nobody other should be able to close it"); + debug!(?pid, "Network has shut down"); } } @@ -877,52 +908,46 @@ impl Drop for Participant { // ignore closed, as we need to send it even though we disconnected the // participant from network let pid = self.remote_pid; - debug!(?pid, "shutting down Participant"); - match self.a2s_disconnect_s.take() { - None => debug!( + debug!(?pid, "Shutting down Participant"); + + match task::block_on(self.a2s_disconnect_s.lock()).take() { + None => trace!( ?pid, "Participant has been shutdown cleanly, no further waiting is requiered!" ), Some(mut a2s_disconnect_s) => { - debug!( - ?pid, - "unclean shutdown detected, active waiting for client to be disconnected" - ); + debug!(?pid, "Disconnect from Scheduler"); task::block_on(async { let (finished_sender, finished_receiver) = oneshot::channel(); a2s_disconnect_s .send((self.remote_pid, finished_sender)) .await - .expect("something is wrong in internal scheduler coding"); - match finished_receiver.await { - Ok(Err(e)) => error!( + .expect("Something is wrong in internal scheduler coding"); + if let Err(e) = finished_receiver + .await + .expect("Something is wrong in internal scheduler/participant coding") + { + error!( ?pid, ?e, "Error while dropping the participant, couldn't send all outgoing \ messages, dropping remaining" - ), - Err(e) => warn!( - ?e, - "//TODO i dont know why the finish doesnt work, i normally would \ - expect to have sended a return message from the participant... \ - ignoring to not caue a panic for now, please fix me" - ), - _ => (), + ); }; }); }, } - debug!(?pid, "network dropped"); + debug!(?pid, "Participant dropped"); } } impl Drop for Stream { fn drop(&mut self) { - // a send if closed is unecessary but doesnt hurt, we must not crash here - if !self.closed.load(Ordering::Relaxed) { + // send if closed is unecessary but doesnt hurt, we must not crash + if !self.send_closed.load(Ordering::Relaxed) { let sid = self.sid; let pid = self.pid; - debug!(?pid, ?sid, "shutting down Stream"); + debug!(?pid, ?sid, "Shutting down Stream"); if task::block_on(self.a2b_close_stream_s.take().unwrap().send(self.sid)).is_err() { warn!( "Other side got already dropped, probably due to timing, other side will \ @@ -932,7 +957,7 @@ impl Drop for Stream { } else { let sid = self.sid; let pid = self.pid; - debug!(?pid, ?sid, "not needed"); + trace!(?pid, ?sid, "Stream Drop not needed"); } } } @@ -940,15 +965,10 @@ impl Drop for Stream { impl std::fmt::Debug for Participant { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let status = if self.closed.load(Ordering::Relaxed) { - "[CLOSED]" - } else { - "[OPEN]" - }; write!( f, - "Participant {{{} local_pid: {:?}, remote_pid: {:?} }}", - status, &self.local_pid, &self.remote_pid, + "Participant {{ local_pid: {:?}, remote_pid: {:?} }}", + &self.local_pid, &self.remote_pid, ) } } @@ -957,10 +977,6 @@ impl From> for StreamError { fn from(_err: crossbeam_channel::SendError) -> Self { StreamError::StreamClosed } } -impl From> for ParticipantError { - fn from(_err: crossbeam_channel::SendError) -> Self { ParticipantError::ParticipantClosed } -} - impl From> for NetworkError { fn from(_err: crossbeam_channel::SendError) -> Self { NetworkError::NetworkClosed } } @@ -969,26 +985,14 @@ impl From for StreamError { fn from(_err: std::option::NoneError) -> Self { StreamError::StreamClosed } } -impl From for ParticipantError { - fn from(_err: std::option::NoneError) -> Self { ParticipantError::ParticipantClosed } -} - impl From for NetworkError { fn from(_err: std::option::NoneError) -> Self { NetworkError::NetworkClosed } } -impl From for ParticipantError { - fn from(_err: mpsc::SendError) -> Self { ParticipantError::ParticipantClosed } -} - impl From for NetworkError { fn from(_err: mpsc::SendError) -> Self { NetworkError::NetworkClosed } } -impl From for ParticipantError { - fn from(_err: oneshot::Canceled) -> Self { ParticipantError::ParticipantClosed } -} - impl From for NetworkError { fn from(_err: oneshot::Canceled) -> Self { NetworkError::NetworkClosed } } @@ -1011,7 +1015,10 @@ impl core::fmt::Display for StreamError { impl core::fmt::Display for ParticipantError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - ParticipantError::ParticipantClosed => write!(f, "participant closed"), + ParticipantError::ParticipantDisconnected => write!(f, "Participant disconnect"), + ParticipantError::ProtocolFailedUnrecoverable => { + write!(f, "underlying protocol failed unrecoverable") + }, } } } @@ -1019,10 +1026,9 @@ impl core::fmt::Display for ParticipantError { impl core::fmt::Display for NetworkError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - NetworkError::NetworkClosed => write!(f, "network closed"), - NetworkError::ListenFailed(_) => write!(f, "listening failed"), - NetworkError::ConnectFailed(_) => write!(f, "connecting failed"), - NetworkError::GracefulDisconnectFailed(_) => write!(f, "graceful disconnect failed"), + NetworkError::NetworkClosed => write!(f, "Network closed"), + NetworkError::ListenFailed(_) => write!(f, "Listening failed"), + NetworkError::ConnectFailed(_) => write!(f, "Connecting failed"), } } } diff --git a/network/src/channel.rs b/network/src/channel.rs index e7dac7134c..25babccd2b 100644 --- a/network/src/channel.rs +++ b/network/src/channel.rs @@ -1,5 +1,6 @@ +#[cfg(feature = "metrics")] +use crate::metrics::NetworkMetrics; use crate::{ - metrics::NetworkMetrics, protocols::Protocols, types::{ Cid, Frame, Pid, Sid, STREAM_ID_OFFSET1, STREAM_ID_OFFSET2, VELOREN_MAGIC_NUMBER, @@ -13,7 +14,7 @@ use futures::{ stream::StreamExt, FutureExt, }; -use std::sync::Arc; +#[cfg(feature = "metrics")] use std::sync::Arc; use tracing::*; pub(crate) struct Channel { @@ -48,13 +49,13 @@ impl Channel { //reapply leftovers from handshake let cnt = leftover_cid_frame.len(); - trace!(?self.cid, ?cnt, "reapplying leftovers"); + trace!(?self.cid, ?cnt, "Reapplying leftovers"); for cid_frame in leftover_cid_frame.drain(..) { w2c_cid_frame_s.send(cid_frame).await.unwrap(); } - trace!(?self.cid, ?cnt, "all leftovers reapplied"); + trace!(?self.cid, ?cnt, "All leftovers reapplied"); - trace!(?self.cid, "start up channel"); + trace!(?self.cid, "Start up channel"); match protocol { Protocols::Tcp(tcp) => { futures::join!( @@ -70,7 +71,7 @@ impl Channel { }, } - trace!(?self.cid, "shut down channel"); + trace!(?self.cid, "Shut down channel"); } } @@ -80,6 +81,7 @@ pub(crate) struct Handshake { local_pid: Pid, secret: u128, init_handshake: bool, + #[cfg(feature = "metrics")] metrics: Arc, } @@ -98,13 +100,14 @@ impl Handshake { cid: u64, local_pid: Pid, secret: u128, - metrics: Arc, + #[cfg(feature = "metrics")] metrics: Arc, init_handshake: bool, ) -> Self { Self { cid, local_pid, secret, + #[cfg(feature = "metrics")] metrics, init_handshake, } @@ -159,114 +162,96 @@ impl Handshake { &self, w2c_cid_frame_r: &mut mpsc::UnboundedReceiver<(Cid, Frame)>, mut c2w_frame_s: mpsc::UnboundedSender, - _read_stop_sender: oneshot::Sender<()>, + read_stop_sender: oneshot::Sender<()>, ) -> Result<(Pid, Sid, u128), ()> { const ERR_S: &str = "Got A Raw Message, these are usually Debug Messages indicating that \ something went wrong on network layer and connection will be closed"; - let mut pid_string = "".to_string(); + #[cfg(feature = "metrics")] let cid_string = self.cid.to_string(); if self.init_handshake { self.send_handshake(&mut c2w_frame_s).await; } - match w2c_cid_frame_r.next().await { - Some(( - _, - Frame::Handshake { - magic_number, - version, - }, - )) => { - trace!(?magic_number, ?version, "recv handshake"); + let frame = w2c_cid_frame_r.next().await.map(|(_cid, frame)| frame); + #[cfg(feature = "metrics")] + { + if let Some(ref frame) = frame { self.metrics .frames_in_total - .with_label_values(&["", &cid_string, "Handshake"]) + .with_label_values(&["", &cid_string, &frame.get_string()]) .inc(); + } + } + let r = match frame { + Some(Frame::Handshake { + magic_number, + version, + }) => { + trace!(?magic_number, ?version, "Recv handshake"); if magic_number != VELOREN_MAGIC_NUMBER { - error!(?magic_number, "connection with invalid magic_number"); + error!(?magic_number, "Connection with invalid magic_number"); #[cfg(debug_assertions)] - { - self.metrics - .frames_out_total - .with_label_values(&["", &cid_string, "Raw"]) - .inc(); - debug!("sending client instructions before killing"); - c2w_frame_s - .send(Frame::Raw(Self::WRONG_NUMBER.to_vec())) - .await - .unwrap(); - c2w_frame_s.send(Frame::Shutdown).await.unwrap(); - } - return Err(()); - } - if version != VELOREN_NETWORK_VERSION { - error!(?version, "connection with wrong network version"); + self.send_raw_and_shutdown(&mut c2w_frame_s, Self::WRONG_NUMBER.to_vec()) + .await; + Err(()) + } else if version != VELOREN_NETWORK_VERSION { + error!(?version, "Connection with wrong network version"); #[cfg(debug_assertions)] - { - debug!("sending client instructions before killing"); - self.metrics - .frames_out_total - .with_label_values(&["", &cid_string, "Raw"]) - .inc(); - c2w_frame_s - .send(Frame::Raw( - format!( - "{} Our Version: {:?}\nYour Version: {:?}\nClosing the \ - connection", - Self::WRONG_VERSION, - VELOREN_NETWORK_VERSION, - version, - ) - .as_bytes() - .to_vec(), - )) - .await - .unwrap(); - c2w_frame_s.send(Frame::Shutdown {}).await.unwrap(); - } - return Err(()); - } - debug!("handshake completed"); - if self.init_handshake { - self.send_init(&mut c2w_frame_s, &pid_string).await; + self.send_raw_and_shutdown( + &mut c2w_frame_s, + format!( + "{} Our Version: {:?}\nYour Version: {:?}\nClosing the connection", + Self::WRONG_VERSION, + VELOREN_NETWORK_VERSION, + version, + ) + .as_bytes() + .to_vec(), + ) + .await; + Err(()) } else { - self.send_handshake(&mut c2w_frame_s).await; + debug!("Handshake completed"); + if self.init_handshake { + self.send_init(&mut c2w_frame_s, "").await; + } else { + self.send_handshake(&mut c2w_frame_s).await; + } + Ok(()) } }, - Some((_, Frame::Shutdown)) => { - info!("shutdown signal received"); - self.metrics - .frames_in_total - .with_label_values(&[&pid_string, &cid_string, "Shutdown"]) - .inc(); - return Err(()); + Some(Frame::Shutdown) => { + info!("Shutdown signal received"); + Err(()) }, - Some((_, Frame::Raw(bytes))) => { - self.metrics - .frames_in_total - .with_label_values(&[&pid_string, &cid_string, "Raw"]) - .inc(); + Some(Frame::Raw(bytes)) => { match std::str::from_utf8(bytes.as_slice()) { Ok(string) => error!(?string, ERR_S), _ => error!(?bytes, ERR_S), } - return Err(()); + Err(()) }, - Some((_, frame)) => { - self.metrics - .frames_in_total - .with_label_values(&[&pid_string, &cid_string, frame.get_string()]) - .inc(); - return Err(()); - }, - None => return Err(()), + Some(_) => Err(()), + None => Err(()), }; + if let Err(()) = r { + if let Err(e) = read_stop_sender.send(()) { + trace!( + ?e, + "couldn't stop protocol, probably it encountered a Protocol Stop and closed \ + itself already, which is fine" + ); + } + return Err(()); + } - match w2c_cid_frame_r.next().await { - Some((_, Frame::Init { pid, secret })) => { + let frame = w2c_cid_frame_r.next().await.map(|(_cid, frame)| frame); + let r = match frame { + Some(Frame::Init { pid, secret }) => { debug!(?pid, "Participant send their ID"); - pid_string = pid.to_string(); + let pid_string = pid.to_string(); + #[cfg(feature = "metrics")] self.metrics .frames_in_total .with_label_values(&[&pid_string, &cid_string, "ParticipantId"]) @@ -277,40 +262,41 @@ impl Handshake { self.send_init(&mut c2w_frame_s, &pid_string).await; STREAM_ID_OFFSET2 }; - info!(?pid, "this Handshake is now configured!"); + info!(?pid, "This Handshake is now configured!"); Ok((pid, stream_id_offset, secret)) }, - Some((_, Frame::Shutdown)) => { - info!("shutdown signal received"); + Some(frame) => { + #[cfg(feature = "metrics")] self.metrics .frames_in_total - .with_label_values(&[&pid_string, &cid_string, "Shutdown"]) + .with_label_values(&["", &cid_string, frame.get_string()]) .inc(); - Err(()) - }, - Some((_, Frame::Raw(bytes))) => { - self.metrics - .frames_in_total - .with_label_values(&[&pid_string, &cid_string, "Raw"]) - .inc(); - match std::str::from_utf8(bytes.as_slice()) { - Ok(string) => error!(?string, ERR_S), - _ => error!(?bytes, ERR_S), + match frame { + Frame::Shutdown => info!("Shutdown signal received"), + Frame::Raw(bytes) => match std::str::from_utf8(bytes.as_slice()) { + Ok(string) => error!(?string, ERR_S), + _ => error!(?bytes, ERR_S), + }, + _ => (), } Err(()) }, - Some((_, frame)) => { - self.metrics - .frames_in_total - .with_label_values(&[&pid_string, &cid_string, frame.get_string()]) - .inc(); - Err(()) - }, None => Err(()), + }; + if r.is_err() { + if let Err(e) = read_stop_sender.send(()) { + trace!( + ?e, + "couldn't stop protocol, probably it encountered a Protocol Stop and closed \ + itself already, which is fine" + ); + } } + r } async fn send_handshake(&self, c2w_frame_s: &mut mpsc::UnboundedSender) { + #[cfg(feature = "metrics")] self.metrics .frames_out_total .with_label_values(&["", &self.cid.to_string(), "Handshake"]) @@ -324,7 +310,13 @@ impl Handshake { .unwrap(); } - async fn send_init(&self, c2w_frame_s: &mut mpsc::UnboundedSender, pid_string: &str) { + async fn send_init( + &self, + c2w_frame_s: &mut mpsc::UnboundedSender, + #[cfg(feature = "metrics")] pid_string: &str, + #[cfg(not(feature = "metrics"))] _pid_string: &str, + ) { + #[cfg(feature = "metrics")] self.metrics .frames_out_total .with_label_values(&[pid_string, &self.cid.to_string(), "ParticipantId"]) @@ -337,4 +329,27 @@ impl Handshake { .await .unwrap(); } + + #[cfg(debug_assertions)] + async fn send_raw_and_shutdown( + &self, + c2w_frame_s: &mut mpsc::UnboundedSender, + data: Vec, + ) { + debug!("Sending client instructions before killing"); + #[cfg(feature = "metrics")] + { + let cid_string = self.cid.to_string(); + self.metrics + .frames_out_total + .with_label_values(&["", &cid_string, "Raw"]) + .inc(); + self.metrics + .frames_out_total + .with_label_values(&["", &cid_string, "Shutdown"]) + .inc(); + } + c2w_frame_s.send(Frame::Raw(data)).await.unwrap(); + c2w_frame_s.send(Frame::Shutdown).await.unwrap(); + } } diff --git a/network/src/lib.rs b/network/src/lib.rs index 36568f2dc7..51d4e7a1e1 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -14,16 +14,16 @@ //! struct [`Network`] once with a new [`Pid`]. The Pid is necessary to identify //! other [`Networks`] over the network protocols (e.g. TCP, UDP) //! -//! To connect to another application, you must know it's [`Address`]. One side -//! will call [`connect`], the other [`connected`]. If successfull both -//! applications will now get a [`Arc`]. +//! To connect to another application, you must know it's [`ProtocolAddr`]. One +//! side will call [`connect`], the other [`connected`]. If successfull both +//! applications will now get a [`Participant`]. //! //! This [`Participant`] represents the connection between those 2 applications. -//! over the respective [`Address`] and with it the choosen network protocol. -//! However messages can't be send directly via [`Participants`], instead you -//! must open a [`Stream`] on it. Like above, one side has to call [`open`], the -//! other [`opened`]. [`Streams`] can have a different priority and -//! [`Promises`]. +//! over the respective [`ProtocolAddr`] and with it the choosen network +//! protocol. However messages can't be send directly via [`Participants`], +//! instead you must open a [`Stream`] on it. Like above, one side has to call +//! [`open`], the other [`opened`]. [`Streams`] can have a different priority +//! and [`Promises`]. //! //! You can now use the [`Stream`] to [`send`] and [`recv`] in both directions. //! You can send all kind of messages that implement [`serde`]. @@ -40,15 +40,15 @@ //! ```rust //! use async_std::task::sleep; //! use futures::{executor::block_on, join}; -//! use veloren_network::{Address, Network, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; +//! use veloren_network::{Network, Pid, ProtocolAddr, PROMISES_CONSISTENCY, PROMISES_ORDERED}; //! //! // Client //! async fn client() -> std::result::Result<(), Box> { //! sleep(std::time::Duration::from_secs(1)).await; // `connect` MUST be after `listen` -//! let (client_network, f) = Network::new(Pid::new(), None); +//! let (client_network, f) = Network::new(Pid::new()); //! std::thread::spawn(f); //! let server = client_network -//! .connect(Address::Tcp("127.0.0.1:12345".parse().unwrap())) +//! .connect(ProtocolAddr::Tcp("127.0.0.1:12345".parse().unwrap())) //! .await?; //! let mut stream = server //! .open(10, PROMISES_ORDERED | PROMISES_CONSISTENCY) @@ -59,15 +59,15 @@ //! //! // Server //! async fn server() -> std::result::Result<(), Box> { -//! let (server_network, f) = Network::new(Pid::new(), None); +//! let (server_network, f) = Network::new(Pid::new()); //! std::thread::spawn(f); //! server_network -//! .listen(Address::Tcp("127.0.0.1:12345".parse().unwrap())) +//! .listen(ProtocolAddr::Tcp("127.0.0.1:12345".parse().unwrap())) //! .await?; //! let client = server_network.connected().await?; //! let mut stream = client.opened().await?; //! let msg: String = stream.recv().await?; -//! println!("got message: {}", msg); +//! println!("Got message: {}", msg); //! assert_eq!(msg, "Hello World"); //! Ok(()) //! } @@ -86,7 +86,6 @@ //! [`Networks`]: crate::api::Network //! [`connect`]: crate::api::Network::connect //! [`connected`]: crate::api::Network::connected -//! [`Arc`]: crate::api::Participant //! [`Participant`]: crate::api::Participant //! [`Participants`]: crate::api::Participant //! [`open`]: crate::api::Participant::open @@ -96,13 +95,13 @@ //! [`send`]: crate::api::Stream::send //! [`recv`]: crate::api::Stream::recv //! [`Pid`]: crate::types::Pid -//! [`Address`]: crate::api::Address +//! [`ProtocolAddr`]: crate::api::ProtocolAddr //! [`Promises`]: crate::types::Promises mod api; mod channel; mod message; -mod metrics; +#[cfg(feature = "metrics")] mod metrics; mod participant; mod prios; mod protocols; @@ -110,7 +109,9 @@ mod scheduler; #[macro_use] mod types; -pub use api::{Address, Network, NetworkError, Participant, ParticipantError, Stream, StreamError}; +pub use api::{ + Network, NetworkError, Participant, ParticipantError, ProtocolAddr, Stream, StreamError, +}; pub use message::MessageBuffer; pub use types::{ Pid, Promises, PROMISES_COMPRESSED, PROMISES_CONSISTENCY, PROMISES_ENCRYPTED, diff --git a/network/src/message.rs b/network/src/message.rs index d1e86f01ec..03626f22d3 100644 --- a/network/src/message.rs +++ b/network/src/message.rs @@ -1,6 +1,6 @@ use serde::{de::DeserializeOwned, Serialize}; //use std::collections::VecDeque; -use crate::types::{Mid, Sid}; +use crate::types::{Frame, Mid, Sid}; use std::{io, sync::Arc}; //Todo: Evaluate switching to VecDeque for quickly adding and removing data @@ -36,19 +36,54 @@ pub(crate) struct IncomingMessage { pub(crate) fn serialize(message: &M) -> MessageBuffer { //this will never fail: https://docs.rs/bincode/0.8.0/bincode/fn.serialize.html let writer = bincode::serialize(message).unwrap(); - MessageBuffer { data: writer } + MessageBuffer { + data: lz4_compress::compress(&writer), + } } //pub(crate) fn deserialize(buffer: MessageBuffer) -> // std::Result> { pub(crate) fn deserialize(buffer: MessageBuffer) -> bincode::Result { - let span = buffer.data; + let span = lz4_compress::decompress(&buffer.data) + .expect("lz4 decompression failed, failed to deserialze"); //this might fail if you choose the wrong type for M. in that case probably X // got transfered while you assume Y. probably this means your application // logic is wrong. E.g. You expect a String, but just get a u8. bincode::deserialize(span.as_slice()) } +impl OutgoingMessage { + pub(crate) const FRAME_DATA_SIZE: u64 = 1400; + + /// returns if msg is empty + pub(crate) fn fill_next>( + &mut self, + msg_sid: Sid, + frames: &mut E, + ) -> bool { + let to_send = std::cmp::min( + self.buffer.data[self.cursor as usize..].len() as u64, + Self::FRAME_DATA_SIZE, + ); + if to_send > 0 { + if self.cursor == 0 { + frames.extend(std::iter::once((msg_sid, Frame::DataHeader { + mid: self.mid, + sid: self.sid, + length: self.buffer.data.len() as u64, + }))); + } + frames.extend(std::iter::once((msg_sid, Frame::Data { + mid: self.mid, + start: self.cursor, + data: self.buffer.data[self.cursor as usize..][..to_send as usize].to_vec(), + }))); + }; + self.cursor += to_send; + self.cursor >= self.buffer.data.len() as u64 + } +} + ///wouldn't trust this aaaassss much, fine for tests pub(crate) fn partial_eq_io_error(first: &io::Error, second: &io::Error) -> bool { if let Some(f) = first.raw_os_error() { @@ -134,13 +169,11 @@ mod tests { fn serialize_test() { let msg = "abc"; let mb = serialize(&msg); - assert_eq!(mb.data.len(), 11); - assert_eq!(mb.data[0], 3); - assert_eq!(mb.data[1], 0); - assert_eq!(mb.data[7], 0); - assert_eq!(mb.data[8], b'a'); - assert_eq!(mb.data[8], 97); - assert_eq!(mb.data[9], b'b'); - assert_eq!(mb.data[10], b'c'); + assert_eq!(mb.data.len(), 9); + assert_eq!(mb.data[0], 34); + assert_eq!(mb.data[1], 3); + assert_eq!(mb.data[6], b'a'); + assert_eq!(mb.data[7], b'b'); + assert_eq!(mb.data[8], b'c'); } } diff --git a/network/src/metrics.rs b/network/src/metrics.rs index cd809c72bc..4436a8ce7f 100644 --- a/network/src/metrics.rs +++ b/network/src/metrics.rs @@ -50,50 +50,50 @@ impl NetworkMetrics { let listen_requests_total = IntCounterVec::new( Opts::new( "listen_requests_total", - "shows the number of listen requests to the scheduler", + "Shows the number of listen requests to the scheduler", ), &["protocol"], )?; let connect_requests_total = IntCounterVec::new( Opts::new( "connect_requests_total", - "shows the number of connect requests to the scheduler", + "Shows the number of connect requests to the scheduler", ), &["protocol"], )?; let participants_connected_total = IntCounter::with_opts(Opts::new( "participants_connected_total", - "shows the number of participants connected to the network", + "Shows the number of participants connected to the network", ))?; let participants_disconnected_total = IntCounter::with_opts(Opts::new( "participants_disconnected_total", - "shows the number of participants disconnected to the network", + "Shows the number of participants disconnected to the network", ))?; let channels_connected_total = IntCounterVec::new( Opts::new( "channels_connected_total", - "number of all channels currently connected on the network", + "Number of all channels currently connected on the network", ), &["participant"], )?; let channels_disconnected_total = IntCounterVec::new( Opts::new( "channels_disconnected_total", - "number of all channels currently disconnected on the network", + "Number of all channels currently disconnected on the network", ), &["participant"], )?; let streams_opened_total = IntCounterVec::new( Opts::new( "streams_opened_total", - "number of all streams currently open on the network", + "Number of all streams currently open on the network", ), &["participant"], )?; let streams_closed_total = IntCounterVec::new( Opts::new( "streams_closed_total", - "number of all streams currently open on the network", + "Number of all streams currently open on the network", ), &["participant"], )?; @@ -112,42 +112,42 @@ impl NetworkMetrics { let frames_out_total = IntCounterVec::new( Opts::new( "frames_out_total", - "number of all frames send per channel, at the channel level", + "Number of all frames send per channel, at the channel level", ), &["participant", "channel", "frametype"], )?; let frames_in_total = IntCounterVec::new( Opts::new( "frames_in_total", - "number of all frames received per channel, at the channel level", + "Number of all frames received per channel, at the channel level", ), &["participant", "channel", "frametype"], )?; let frames_wire_out_total = IntCounterVec::new( Opts::new( "frames_wire_out_total", - "number of all frames send per channel, at the protocol level", + "Number of all frames send per channel, at the protocol level", ), &["channel", "frametype"], )?; let frames_wire_in_total = IntCounterVec::new( Opts::new( "frames_wire_in_total", - "number of all frames received per channel, at the protocol level", + "Number of all frames received per channel, at the protocol level", ), &["channel", "frametype"], )?; let wire_out_throughput = IntCounterVec::new( Opts::new( "wire_out_throughput", - "throupgput of all data frames send per channel, at the protocol level", + "Throupgput of all data frames send per channel, at the protocol level", ), &["channel"], )?; let wire_in_throughput = IntCounterVec::new( Opts::new( "wire_in_throughput", - "throupgput of all data frames send per channel, at the protocol level", + "Throupgput of all data frames send per channel, at the protocol level", ), &["channel"], )?; @@ -155,7 +155,7 @@ impl NetworkMetrics { let message_out_total = IntCounterVec::new( Opts::new( "message_out_total", - "number of messages send by streams on the network", + "Number of messages send by streams on the network", ), &["participant", "stream"], )?; @@ -163,28 +163,28 @@ impl NetworkMetrics { let message_out_throughput = IntCounterVec::new( Opts::new( "message_out_throughput", - "throughput of messages send by streams on the network", + "Throughput of messages send by streams on the network", ), &["participant", "stream"], )?; let queued_count = IntGaugeVec::new( Opts::new( "queued_count", - "queued number of messages by participant on the network", + "Queued number of messages by participant on the network", ), &["channel"], )?; let queued_bytes = IntGaugeVec::new( Opts::new( "queued_bytes", - "queued bytes of messages by participant on the network", + "Queued bytes of messages by participant on the network", ), &["channel"], )?; let participants_ping = IntGaugeVec::new( Opts::new( "participants_ping", - "ping time to participants on the network", + "Ping time to participants on the network", ), &["channel"], )?; diff --git a/network/src/participant.rs b/network/src/participant.rs index 28647b234d..d2035b9989 100644 --- a/network/src/participant.rs +++ b/network/src/participant.rs @@ -1,8 +1,9 @@ +#[cfg(feature = "metrics")] +use crate::metrics::{NetworkMetrics, PidCidFrameCache}; use crate::{ - api::Stream, + api::{ParticipantError, Stream}, channel::Channel, message::{IncomingMessage, MessageBuffer, OutgoingMessage}, - metrics::{NetworkMetrics, PidCidFrameCache}, prios::PrioManager, protocols::Protocols, types::{Cid, Frame, Pid, Prio, Promises, Sid}, @@ -25,6 +26,11 @@ use std::{ }; use tracing::*; +pub(crate) type A2bStreamOpen = (Prio, Promises, oneshot::Sender); +pub(crate) type S2bCreateChannel = (Cid, Sid, Protocols, Vec<(Cid, Frame)>, oneshot::Sender<()>); +pub(crate) type S2bShutdownBparticipant = oneshot::Sender>; +pub(crate) type B2sPrioStatistic = (Pid, u64, u64); + #[derive(Debug)] struct ChannelInfo { cid: Cid, @@ -37,20 +43,25 @@ struct ChannelInfo { struct StreamInfo { prio: Prio, promises: Promises, + send_closed: Arc, b2a_msg_recv_s: mpsc::UnboundedSender, - closed: Arc, } #[derive(Debug)] -#[allow(clippy::type_complexity)] struct ControlChannels { - a2b_steam_open_r: mpsc::UnboundedReceiver<(Prio, Promises, oneshot::Sender)>, + a2b_stream_open_r: mpsc::UnboundedReceiver, b2a_stream_opened_s: mpsc::UnboundedSender, - s2b_create_channel_r: - mpsc::UnboundedReceiver<(Cid, Sid, Protocols, Vec<(Cid, Frame)>, oneshot::Sender<()>)>, + s2b_create_channel_r: mpsc::UnboundedReceiver, a2b_close_stream_r: mpsc::UnboundedReceiver, a2b_close_stream_s: mpsc::UnboundedSender, - s2b_shutdown_bparticipant_r: oneshot::Receiver>>, /* own */ + s2b_shutdown_bparticipant_r: oneshot::Receiver, /* own */ +} + +#[derive(Debug)] +struct ShutdownInfo { + //a2b_stream_open_r: mpsc::UnboundedReceiver, + b2a_stream_opened_s: mpsc::UnboundedSender, + error: Option, } #[derive(Debug)] @@ -62,8 +73,10 @@ pub struct BParticipant { streams: RwLock>, running_mgr: AtomicUsize, run_channels: Option, + #[cfg(feature = "metrics")] metrics: Arc, no_channel_error_info: RwLock<(Instant, u64)>, + shutdown_info: RwLock, } impl BParticipant { @@ -71,23 +84,28 @@ impl BParticipant { pub(crate) fn new( remote_pid: Pid, offset_sid: Sid, - metrics: Arc, + #[cfg(feature = "metrics")] metrics: Arc, ) -> ( Self, - mpsc::UnboundedSender<(Prio, Promises, oneshot::Sender)>, + mpsc::UnboundedSender, mpsc::UnboundedReceiver, - mpsc::UnboundedSender<(Cid, Sid, Protocols, Vec<(Cid, Frame)>, oneshot::Sender<()>)>, - oneshot::Sender>>, + mpsc::UnboundedSender, + oneshot::Sender, ) { - let (a2b_steam_open_s, a2b_steam_open_r) = - mpsc::unbounded::<(Prio, Promises, oneshot::Sender)>(); + let (a2b_steam_open_s, a2b_stream_open_r) = mpsc::unbounded::(); let (b2a_stream_opened_s, b2a_stream_opened_r) = mpsc::unbounded::(); let (a2b_close_stream_s, a2b_close_stream_r) = mpsc::unbounded(); let (s2b_shutdown_bparticipant_s, s2b_shutdown_bparticipant_r) = oneshot::channel(); let (s2b_create_channel_s, s2b_create_channel_r) = mpsc::unbounded(); + let shutdown_info = RwLock::new(ShutdownInfo { + //a2b_stream_open_r: a2b_stream_open_r.clone(), + b2a_stream_opened_s: b2a_stream_opened_s.clone(), + error: None, + }); + let run_channels = Some(ControlChannels { - a2b_steam_open_r, + a2b_stream_open_r, b2a_stream_opened_s, s2b_create_channel_r, a2b_close_stream_r, @@ -104,8 +122,10 @@ impl BParticipant { streams: RwLock::new(HashMap::new()), running_mgr: AtomicUsize::new(0), run_channels, + #[cfg(feature = "metrics")] metrics, no_channel_error_info: RwLock::new((Instant::now(), 0)), + shutdown_info, }, a2b_steam_open_s, b2a_stream_opened_r, @@ -114,7 +134,7 @@ impl BParticipant { ) } - pub async fn run(mut self, b2s_prio_statistic_s: mpsc::UnboundedSender<(Pid, u64, u64)>) { + pub async fn run(mut self, b2s_prio_statistic_s: mpsc::UnboundedSender) { //those managers that listen on api::Participant need an additional oneshot for // shutdown scenario, those handled by scheduler will be closed by it. let (shutdown_send_mgr_sender, shutdown_send_mgr_receiver) = oneshot::channel(); @@ -123,13 +143,16 @@ impl BParticipant { let (shutdown_open_mgr_sender, shutdown_open_mgr_receiver) = oneshot::channel(); let (b2b_prios_flushed_s, b2b_prios_flushed_r) = oneshot::channel(); let (w2b_frames_s, w2b_frames_r) = mpsc::unbounded::<(Cid, Frame)>(); - let (prios, a2p_msg_s, b2p_notify_empty_stream_s) = - PrioManager::new(self.metrics.clone(), self.remote_pid_string.clone()); + let (prios, a2p_msg_s, b2p_notify_empty_stream_s) = PrioManager::new( + #[cfg(feature = "metrics")] + self.metrics.clone(), + self.remote_pid_string.clone(), + ); let run_channels = self.run_channels.take().unwrap(); futures::join!( self.open_mgr( - run_channels.a2b_steam_open_r, + run_channels.a2b_stream_open_r, run_channels.a2b_close_stream_s.clone(), a2p_msg_s.clone(), shutdown_open_mgr_receiver, @@ -155,11 +178,11 @@ impl BParticipant { self.participant_shutdown_mgr( run_channels.s2b_shutdown_bparticipant_r, b2b_prios_flushed_r, - vec!( + vec![ shutdown_send_mgr_sender, shutdown_open_mgr_sender, - shutdown_stream_close_mgr_sender - ) + shutdown_stream_close_mgr_sender, + ], ), ); } @@ -169,7 +192,7 @@ impl BParticipant { mut prios: PrioManager, mut shutdown_send_mgr_receiver: oneshot::Receiver<()>, b2b_prios_flushed_s: oneshot::Sender<()>, - mut b2s_prio_statistic_s: mpsc::UnboundedSender<(Pid, u64, u64)>, + mut b2s_prio_statistic_s: mpsc::UnboundedSender, ) { //This time equals the MINIMUM Latency in average, so keep it down and //Todo: // make it configureable or switch to await E.g. Prio 0 = await, prio 50 @@ -178,19 +201,24 @@ impl BParticipant { const FRAMES_PER_TICK: usize = 10005; self.running_mgr.fetch_add(1, Ordering::Relaxed); let mut closing_up = false; - trace!("start send_mgr"); + trace!("Start send_mgr"); + #[cfg(feature = "metrics")] let mut send_cache = PidCidFrameCache::new(self.metrics.frames_out_total.clone(), self.remote_pid); - //while !self.closed.load(Ordering::Relaxed) { loop { let mut frames = VecDeque::new(); prios.fill_frames(FRAMES_PER_TICK, &mut frames).await; let len = frames.len(); if len > 0 { - trace!("tick {}", len); + trace!("Tick {}", len); } for (_, frame) in frames { - self.send_frame(frame, &mut send_cache).await; + self.send_frame( + frame, + #[cfg(feature = "metrics")] + &mut send_cache, + ) + .await; } b2s_prio_statistic_s .send((self.remote_pid, len as u64, /* */ 0)) @@ -207,7 +235,7 @@ impl BParticipant { closing_up = true; } } - trace!("stop send_mgr"); + trace!("Stop send_mgr"); b2b_prios_flushed_s.send(()).unwrap(); self.running_mgr.fetch_sub(1, Ordering::Relaxed); } @@ -218,7 +246,7 @@ impl BParticipant { async fn send_frame( &self, frame: Frame, - frames_out_total_cache: &mut PidCidFrameCache, + #[cfg(feature = "metrics")] frames_out_total_cache: &mut PidCidFrameCache, ) -> bool { // find out ideal channel here //TODO: just take first @@ -227,27 +255,31 @@ impl BParticipant { //note: this is technically wrong we should only increase when it suceeded, but // this requiered me to clone `frame` which is a to big performance impact for // error handling + #[cfg(feature = "metrics")] frames_out_total_cache .with_label_values(ci.cid, &frame) .inc(); if let Err(e) = ci.b2w_frame_s.send(frame).await { warn!( ?e, - "the channel got closed unexpectedly, cleaning it up now." + "The channel got closed unexpectedly, cleaning it up now." ); let ci = lock.remove(0); if let Err(e) = ci.b2r_read_shutdown.send(()) { debug!( ?e, - "error shutdowning channel, which is prob fine as we detected it to no \ + "Error shutdowning channel, which is prob fine as we detected it to no \ longer work in the first place" ); }; - //TODO - warn!( + //TODO FIXME tags: takeover channel multiple + info!( "FIXME: the frame is actually drop. which is fine for now as the participant \ will be closed, but not if we do channel-takeover" ); + //TEMP FIX: as we dont have channel takeover yet drop the whole bParticipant + self.close_api(Some(ParticipantError::ProtocolFailedUnrecoverable)) + .await; false } else { true @@ -259,7 +291,7 @@ impl BParticipant { guard.0 = now; let occurrences = guard.1 + 1; guard.1 = 0; - error!(?occurrences, "participant has no channel to communicate on"); + error!(?occurrences, "Participant has no channel to communicate on"); } else { guard.1 += 1; } @@ -275,19 +307,24 @@ impl BParticipant { a2p_msg_s: crossbeam_channel::Sender<(Prio, Sid, OutgoingMessage)>, ) { self.running_mgr.fetch_add(1, Ordering::Relaxed); - trace!("start handle_frames_mgr"); + trace!("Start handle_frames_mgr"); let mut messages = HashMap::new(); let mut dropped_instant = Instant::now(); let mut dropped_cnt = 0u64; let mut dropped_sid = Sid::new(0); while let Some((cid, frame)) = w2b_frames_r.next().await { - let cid_string = cid.to_string(); //trace!("handling frame"); - self.metrics - .frames_in_total - .with_label_values(&[&self.remote_pid_string, &cid_string, frame.get_string()]) - .inc(); + #[cfg(feature = "metrics")] + { + let cid_string = cid.to_string(); + self.metrics + .frames_in_total + .with_label_values(&[&self.remote_pid_string, &cid_string, frame.get_string()]) + .inc(); + } + #[cfg(not(feature = "metrics"))] + let _cid = cid; match frame { Frame::OpenStream { sid, @@ -299,7 +336,7 @@ impl BParticipant { .create_stream(sid, prio, promises, a2p_msg_s, &a2b_close_stream_s) .await; b2a_stream_opened_s.send(stream).await.unwrap(); - trace!("opened frame from remote"); + trace!("Opened frame from remote"); }, Frame::CloseStream { sid } => { // Closing is realised by setting a AtomicBool to true, however we also have a @@ -309,21 +346,23 @@ impl BParticipant { // be dropped... from remote, notify local trace!( ?sid, - "got remote request to close a stream, without flushing it, local \ + "Got remote request to close a stream, without flushing it, local \ messages are dropped" ); // no wait for flush here, as the remote wouldn't care anyway. if let Some(si) = self.streams.write().await.remove(&sid) { + #[cfg(feature = "metrics")] self.metrics .streams_closed_total .with_label_values(&[&self.remote_pid_string]) .inc(); - si.closed.store(true, Ordering::Relaxed); - trace!(?sid, "closed stream from remote"); + si.send_closed.store(true, Ordering::Relaxed); + si.b2a_msg_recv_s.close_channel(); + trace!(?sid, "Closed stream from remote"); } else { warn!( ?sid, - "couldn't find stream to close, either this is a duplicate message, \ + "Couldn't find stream to close, either this is a duplicate message, \ or the local copy of the Stream got closed simultaniously" ); } @@ -356,7 +395,7 @@ impl BParticipant { warn!( ?e, ?mid, - "dropping message, as streams seem to be in act of beeing \ + "Dropping message, as streams seem to be in act of beeing \ dropped right now" ); } @@ -368,7 +407,7 @@ impl BParticipant { { warn!( ?dropped_cnt, - "dropping multiple messages as stream no longer seems to \ + "Dropping multiple messages as stream no longer seems to \ exist because it was dropped probably." ); dropped_cnt = 0; @@ -380,38 +419,32 @@ impl BParticipant { } } }, - Frame::Shutdown => error!( - "Somehow this Shutdown signal got here, i should probably handle it. To not \ - crash let me just put this message here" - ), - f => unreachable!("never reaches frame!: {:?}", f), + Frame::Shutdown => { + debug!("Shutdown received from remote side"); + self.close_api(Some(ParticipantError::ParticipantDisconnected)) + .await; + }, + f => unreachable!("Frame should never reache participant!: {:?}", f), } } if dropped_cnt > 0 { warn!( ?dropped_cnt, - "dropping multiple messages as stream no longer seems to exist because it was \ + "Dropping multiple messages as stream no longer seems to exist because it was \ dropped probably." ); } - trace!("stop handle_frames_mgr"); + trace!("Stop handle_frames_mgr"); self.running_mgr.fetch_sub(1, Ordering::Relaxed); } - #[allow(clippy::type_complexity)] async fn create_channel_mgr( &self, - s2b_create_channel_r: mpsc::UnboundedReceiver<( - Cid, - Sid, - Protocols, - Vec<(Cid, Frame)>, - oneshot::Sender<()>, - )>, + s2b_create_channel_r: mpsc::UnboundedReceiver, w2b_frames_s: mpsc::UnboundedSender<(Cid, Frame)>, ) { self.running_mgr.fetch_add(1, Ordering::Relaxed); - trace!("start create_channel_mgr"); + trace!("Start create_channel_mgr"); s2b_create_channel_r .for_each_concurrent( None, @@ -429,46 +462,56 @@ impl BParticipant { b2r_read_shutdown, }); b2s_create_channel_done_s.send(()).unwrap(); + #[cfg(feature = "metrics")] self.metrics .channels_connected_total .with_label_values(&[&self.remote_pid_string]) .inc(); - trace!(?cid, "running channel in participant"); + trace!(?cid, "Running channel in participant"); channel .run(protocol, w2b_frames_s, leftover_cid_frame) .await; + #[cfg(feature = "metrics")] self.metrics .channels_disconnected_total .with_label_values(&[&self.remote_pid_string]) .inc(); - trace!(?cid, "channel got closed"); + trace!(?cid, "Channel got closed"); } }, ) .await; - trace!("stop create_channel_mgr"); + trace!("Stop create_channel_mgr"); self.running_mgr.fetch_sub(1, Ordering::Relaxed); } async fn open_mgr( &self, - mut a2b_steam_open_r: mpsc::UnboundedReceiver<(Prio, Promises, oneshot::Sender)>, + mut a2b_stream_open_r: mpsc::UnboundedReceiver, a2b_close_stream_s: mpsc::UnboundedSender, a2p_msg_s: crossbeam_channel::Sender<(Prio, Sid, OutgoingMessage)>, shutdown_open_mgr_receiver: oneshot::Receiver<()>, ) { self.running_mgr.fetch_add(1, Ordering::Relaxed); - trace!("start open_mgr"); + trace!("Start open_mgr"); let mut stream_ids = self.offset_sid; + #[cfg(feature = "metrics")] let mut send_cache = PidCidFrameCache::new(self.metrics.frames_out_total.clone(), self.remote_pid); let mut shutdown_open_mgr_receiver = shutdown_open_mgr_receiver.fuse(); //from api or shutdown signal while let Some((prio, promises, p2a_return_stream)) = select! { - next = a2b_steam_open_r.next().fuse() => next, + next = a2b_stream_open_r.next().fuse() => next, _ = shutdown_open_mgr_receiver => None, } { - debug!(?prio, ?promises, "got request to open a new steam"); + debug!(?prio, ?promises, "Got request to open a new steam"); + //TODO: a2b_stream_open_r isn't closed on api_close yet. This needs to change. + //till then just check here if we are closed and in that case do nothing (not + // even answer) + if self.shutdown_info.read().await.error.is_some() { + continue; + } + let a2p_msg_s = a2p_msg_s.clone(); let sid = stream_ids; let stream = self @@ -481,6 +524,7 @@ impl BParticipant { prio, promises, }, + #[cfg(feature = "metrics")] &mut send_cache, ) .await @@ -491,7 +535,7 @@ impl BParticipant { stream_ids += Sid::from(1); } } - trace!("stop open_mgr"); + trace!("Stop open_mgr"); self.running_mgr.fetch_sub(1, Ordering::Relaxed); } @@ -499,52 +543,66 @@ impl BParticipant { /// wait for everything to go right! Then return 1. Shutting down /// Streams for API and End user! 2. Wait for all "prio queued" Messages /// to be send. 3. Send Stream + /// If BParticipant kills itself managers stay active till this function is + /// called by api to get the result status async fn participant_shutdown_mgr( &self, - s2b_shutdown_bparticipant_r: oneshot::Receiver>>, + s2b_shutdown_bparticipant_r: oneshot::Receiver, b2b_prios_flushed_r: oneshot::Receiver<()>, - mut to_shutdown: Vec>, + mut mgr_to_shutdown: Vec>, ) { self.running_mgr.fetch_add(1, Ordering::Relaxed); - trace!("start participant_shutdown_mgr"); + trace!("Start participant_shutdown_mgr"); let sender = s2b_shutdown_bparticipant_r.await.unwrap(); - debug!("closing all managers"); - for sender in to_shutdown.drain(..) { + + self.close_api(None).await; + + debug!("Closing all managers"); + for sender in mgr_to_shutdown.drain(..) { if let Err(e) = sender.send(()) { - warn!(?e, "manager seems to be closed already, weird, maybe a bug"); + warn!(?e, "Manager seems to be closed already, weird, maybe a bug"); }; } - debug!("closing all streams"); - for (sid, si) in self.streams.write().await.drain() { - trace!(?sid, "shutting down Stream"); - si.closed.store(true, Ordering::Relaxed); - } - debug!("waiting for prios to be flushed"); + b2b_prios_flushed_r.await.unwrap(); - debug!("closing all channels"); + debug!("Closing all channels, after flushed prios"); for ci in self.channels.write().await.drain(..) { if let Err(e) = ci.b2r_read_shutdown.send(()) { - debug!(?e, ?ci.cid, "seems like this read protocol got already dropped by closing the Stream itself, just ignoring the fact"); + debug!(?e, ?ci.cid, "Seems like this read protocol got already dropped by closing the Stream itself, just ignoring the fact"); }; } + //Wait for other bparticipants mgr to close via AtomicUsize const SLEEP_TIME: Duration = Duration::from_millis(5); + const ALLOWED_MANAGER: usize = 1; async_std::task::sleep(SLEEP_TIME).await; let mut i: u32 = 1; - while self.running_mgr.load(Ordering::Relaxed) > 1 { + while self.running_mgr.load(Ordering::Relaxed) > ALLOWED_MANAGER { i += 1; if i.rem_euclid(10) == 1 { trace!( - "waiting for bparticipant mgr to shut down, remaining {}", - self.running_mgr.load(Ordering::Relaxed) - 1 + ?ALLOWED_MANAGER, + "Waiting for bparticipant mgr to shut down, remaining {}", + self.running_mgr.load(Ordering::Relaxed) - ALLOWED_MANAGER ); } async_std::task::sleep(SLEEP_TIME * i).await; } - trace!("all bparticipant mgr (except me) are shut down now"); + trace!("All BParticipant mgr (except me) are shut down now"); + + #[cfg(feature = "metrics")] self.metrics.participants_disconnected_total.inc(); - sender.send(Ok(())).unwrap(); - trace!("stop participant_shutdown_mgr"); + debug!("BParticipant close done"); + + let mut lock = self.shutdown_info.write().await; + sender + .send(match lock.error.take() { + None => Ok(()), + Some(e) => Err(e), + }) + .unwrap(); + + trace!("Stop participant_shutdown_mgr"); self.running_mgr.fetch_sub(1, Ordering::Relaxed); } @@ -555,7 +613,8 @@ impl BParticipant { b2p_notify_empty_stream_s: crossbeam_channel::Sender<(Sid, oneshot::Sender<()>)>, ) { self.running_mgr.fetch_add(1, Ordering::Relaxed); - trace!("start stream_close_mgr"); + trace!("Start stream_close_mgr"); + #[cfg(feature = "metrics")] let mut send_cache = PidCidFrameCache::new(self.metrics.frames_out_total.clone(), self.remote_pid); let mut shutdown_stream_close_mgr_receiver = shutdown_stream_close_mgr_receiver.fuse(); @@ -567,7 +626,7 @@ impl BParticipant { } { //TODO: make this concurrent! //TODO: Performance, closing is slow! - trace!(?sid, "got request from api to close steam"); + trace!(?sid, "Got request from api to close steam"); //This needs to first stop clients from sending any more. //Then it will wait for all pending messages (in prio) to be send to the // protocol After this happened the stream is closed @@ -575,34 +634,40 @@ impl BParticipant { // frame! If we would send it before, all followup messages couldn't // be handled at the remote side. - trace!(?sid, "stopping api to use this stream"); + trace!(?sid, "Stopping api to use this stream"); match self.streams.read().await.get(&sid) { Some(si) => { - si.closed.store(true, Ordering::Relaxed); + si.send_closed.store(true, Ordering::Relaxed); + si.b2a_msg_recv_s.close_channel(); }, - None => warn!("couldn't find the stream, might be simulanious close from remote"), + None => warn!("Couldn't find the stream, might be simulanious close from remote"), } //TODO: what happens if RIGHT NOW the remote sends a StreamClose and this // streams get closed and removed? RACE CONDITION - trace!(?sid, "wait for stream to be flushed"); + trace!(?sid, "Wait for stream to be flushed"); let (s2b_stream_finished_closed_s, s2b_stream_finished_closed_r) = oneshot::channel(); b2p_notify_empty_stream_s .send((sid, s2b_stream_finished_closed_s)) .unwrap(); s2b_stream_finished_closed_r.await.unwrap(); - trace!(?sid, "stream was successfully flushed"); + trace!(?sid, "Stream was successfully flushed"); + #[cfg(feature = "metrics")] self.metrics .streams_closed_total .with_label_values(&[&self.remote_pid_string]) .inc(); //only now remove the Stream, that means we can still recv on it. self.streams.write().await.remove(&sid); - self.send_frame(Frame::CloseStream { sid }, &mut send_cache) - .await; + self.send_frame( + Frame::CloseStream { sid }, + #[cfg(feature = "metrics")] + &mut send_cache, + ) + .await; } - trace!("stop stream_close_mgr"); + trace!("Stop stream_close_mgr"); self.running_mgr.fetch_sub(1, Ordering::Relaxed); } @@ -615,13 +680,14 @@ impl BParticipant { a2b_close_stream_s: &mpsc::UnboundedSender, ) -> Stream { let (b2a_msg_recv_s, b2a_msg_recv_r) = mpsc::unbounded::(); - let closed = Arc::new(AtomicBool::new(false)); + let send_closed = Arc::new(AtomicBool::new(false)); self.streams.write().await.insert(sid, StreamInfo { prio, promises, + send_closed: send_closed.clone(), b2a_msg_recv_s, - closed: closed.clone(), }); + #[cfg(feature = "metrics")] self.metrics .streams_opened_total .with_label_values(&[&self.remote_pid_string]) @@ -631,16 +697,28 @@ impl BParticipant { sid, prio, promises, + send_closed, a2p_msg_s, b2a_msg_recv_r, - closed.clone(), a2b_close_stream_s.clone(), ) } - /* - async fn close_participant(&self) { + /// close streams and set err + async fn close_api(&self, reason: Option) { + //closing api::Participant is done by closing all channels, exepct for the + // shutdown channel at this point! + let mut lock = self.shutdown_info.write().await; + if let Some(r) = reason { + lock.error = Some(r); + } + lock.b2a_stream_opened_s.close_channel(); + debug!("Closing all streams"); + for (sid, si) in self.streams.write().await.drain() { + trace!(?sid, "Shutting down Stream"); + si.b2a_msg_recv_s.close_channel(); + si.send_closed.store(true, Ordering::Relaxed); + } } - */ } diff --git a/network/src/prios.rs b/network/src/prios.rs index b3a06c4d0b..4e5971a32e 100644 --- a/network/src/prios.rs +++ b/network/src/prios.rs @@ -4,18 +4,17 @@ //!E.g. in the same time 100 prio0 messages are send, only 50 prio5, 25 prio10, //! 12 prio15 or 6 prio20 messages are send. Note: TODO: prio0 will be send //! immeadiatly when found! - +//! +#[cfg(feature = "metrics")] +use crate::metrics::NetworkMetrics; use crate::{ message::OutgoingMessage, - metrics::NetworkMetrics, types::{Frame, Prio, Sid}, }; use crossbeam_channel::{unbounded, Receiver, Sender}; use futures::channel::oneshot; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - sync::Arc, -}; +use std::collections::{HashMap, HashSet, VecDeque}; +#[cfg(feature = "metrics")] use std::sync::Arc; use tracing::*; @@ -35,12 +34,13 @@ pub(crate) struct PrioManager { //you can register to be notified if a pid_sid combination is flushed completly here sid_flushed_rx: Receiver<(Sid, oneshot::Sender<()>)>, queued: HashSet, + #[cfg(feature = "metrics")] metrics: Arc, + #[cfg(feature = "metrics")] pid: String, } impl PrioManager { - const FRAME_DATA_SIZE: u64 = 1400; const PRIOS: [u32; PRIO_MAX] = [ 100, 115, 132, 152, 174, 200, 230, 264, 303, 348, 400, 459, 528, 606, 696, 800, 919, 1056, 1213, 1393, 1600, 1838, 2111, 2425, 2786, 3200, 3676, 4222, 4850, 5572, 6400, 7352, 8445, @@ -51,13 +51,15 @@ impl PrioManager { #[allow(clippy::type_complexity)] pub fn new( - metrics: Arc, + #[cfg(feature = "metrics")] metrics: Arc, pid: String, ) -> ( Self, Sender<(Prio, Sid, OutgoingMessage)>, Sender<(Sid, oneshot::Sender<()>)>, ) { + #[cfg(not(feature = "metrics"))] + let _pid = pid; // (a2p_msg_s, a2p_msg_r) let (messages_tx, messages_rx) = unbounded(); let (sid_flushed_tx, sid_flushed_rx) = unbounded(); @@ -134,7 +136,9 @@ impl PrioManager { queued: HashSet::new(), //TODO: optimize with u64 and 64 bits sid_flushed_rx, sid_owned: HashMap::new(), + #[cfg(feature = "metrics")] metrics, + #[cfg(feature = "metrics")] pid, }, messages_tx, @@ -149,15 +153,19 @@ impl PrioManager { for (prio, sid, msg) in self.messages_rx.try_iter() { debug_assert!(prio as usize <= PRIO_MAX); messages += 1; - let sid_string = sid.to_string(); - self.metrics - .message_out_total - .with_label_values(&[&self.pid, &sid_string]) - .inc(); - self.metrics - .message_out_throughput - .with_label_values(&[&self.pid, &sid_string]) - .inc_by(msg.buffer.data.len() as i64); + #[cfg(feature = "metrics")] + { + let sid_string = sid.to_string(); + self.metrics + .message_out_total + .with_label_values(&[&self.pid, &sid_string]) + .inc(); + self.metrics + .message_out_throughput + .with_label_values(&[&self.pid, &sid_string]) + .inc_by(msg.buffer.data.len() as i64); + } + //trace!(?prio, ?sid_string, "tick"); self.queued.insert(prio); self.messages[prio as usize].push_back((sid, msg)); @@ -201,34 +209,6 @@ impl PrioManager { .min_by_key(|&n| self.points[*n as usize]).cloned()*/ } - /// returns if msg is empty - fn tick_msg>( - msg: &mut OutgoingMessage, - msg_sid: Sid, - frames: &mut E, - ) -> bool { - let to_send = std::cmp::min( - msg.buffer.data[msg.cursor as usize..].len() as u64, - Self::FRAME_DATA_SIZE, - ); - if to_send > 0 { - if msg.cursor == 0 { - frames.extend(std::iter::once((msg_sid, Frame::DataHeader { - mid: msg.mid, - sid: msg.sid, - length: msg.buffer.data.len() as u64, - }))); - } - frames.extend(std::iter::once((msg_sid, Frame::Data { - mid: msg.mid, - start: msg.cursor, - data: msg.buffer.data[msg.cursor as usize..][..to_send as usize].to_vec(), - }))); - }; - msg.cursor += to_send; - msg.cursor >= msg.buffer.data.len() as u64 - } - /// no_of_frames = frames.len() /// Your goal is to try to find a realistic no_of_frames! /// no_of_frames should be choosen so, that all Frames can be send out till @@ -257,7 +237,7 @@ impl PrioManager { // => messages with same prio get a fair chance :) //TODO: evalaute not poping every time let (sid, mut msg) = self.messages[prio as usize].pop_front().unwrap(); - if Self::tick_msg(&mut msg, sid, frames) { + if msg.fill_next(sid, frames) { //trace!(?m.mid, "finish message"); //check if prio is empty if self.messages[prio as usize].is_empty() { @@ -265,7 +245,7 @@ impl PrioManager { } //decrease pid_sid counter by 1 again let cnt = self.sid_owned.get_mut(&sid).expect( - "the pid_sid_owned counter works wrong, more pid,sid removed than \ + "The pid_sid_owned counter works wrong, more pid,sid removed than \ inserted", ); cnt.len -= 1; @@ -276,7 +256,7 @@ impl PrioManager { } } } else { - trace!(?msg.mid, "repush message"); + trace!(?msg.mid, "Repush message"); self.messages[prio as usize].push_front((sid, msg)); } }, @@ -314,8 +294,8 @@ mod tests { use futures::{channel::oneshot, executor::block_on}; use std::{collections::VecDeque, sync::Arc}; - const SIZE: u64 = PrioManager::FRAME_DATA_SIZE; - const USIZE: usize = PrioManager::FRAME_DATA_SIZE as usize; + const SIZE: u64 = OutgoingMessage::FRAME_DATA_SIZE; + const USIZE: usize = OutgoingMessage::FRAME_DATA_SIZE as usize; #[allow(clippy::type_complexity)] fn mock_new() -> ( @@ -358,28 +338,28 @@ mod tests { fn assert_header(frames: &mut VecDeque<(Sid, Frame)>, f_sid: u64, f_length: u64) { let frame = frames .pop_front() - .expect("frames vecdeque doesn't contain enough frames!") + .expect("Frames vecdeque doesn't contain enough frames!") .1; if let Frame::DataHeader { mid, sid, length } = frame { assert_eq!(mid, 1); assert_eq!(sid, Sid::new(f_sid)); assert_eq!(length, f_length); } else { - panic!("wrong frame type!, expected DataHeader"); + panic!("Wrong frame type!, expected DataHeader"); } } fn assert_data(frames: &mut VecDeque<(Sid, Frame)>, f_start: u64, f_data: Vec) { let frame = frames .pop_front() - .expect("frames vecdeque doesn't contain enough frames!") + .expect("Frames vecdeque doesn't contain enough frames!") .1; if let Frame::Data { mid, start, data } = frame { assert_eq!(mid, 1); assert_eq!(start, f_start); assert_eq!(data, f_data); } else { - panic!("wrong frame type!, expected Data"); + panic!("Wrong frame type!, expected Data"); } } diff --git a/network/src/protocols.rs b/network/src/protocols.rs index 3d918008c0..687e1d8885 100644 --- a/network/src/protocols.rs +++ b/network/src/protocols.rs @@ -1,20 +1,19 @@ -use crate::{ - metrics::{CidFrameCache, NetworkMetrics}, - types::{Cid, Frame, Mid, Pid, Sid}, -}; +#[cfg(feature = "metrics")] +use crate::metrics::{CidFrameCache, NetworkMetrics}; +use crate::types::{Cid, Frame, Mid, Pid, Sid}; use async_std::{ net::{TcpStream, UdpSocket}, prelude::*, }; use futures::{ channel::{mpsc, oneshot}, - future::FutureExt, + future::{Fuse, FutureExt}, lock::Mutex, select, sink::SinkExt, stream::StreamExt, }; -use std::{net::SocketAddr, sync::Arc}; +use std::{convert::TryFrom, net::SocketAddr, sync::Arc}; use tracing::*; // Reserving bytes 0, 10, 13 as i have enough space and want to make it easy to @@ -41,6 +40,7 @@ pub(crate) enum Protocols { #[derive(Debug)] pub(crate) struct TcpProtocol { stream: TcpStream, + #[cfg(feature = "metrics")] metrics: Arc, } @@ -48,33 +48,54 @@ pub(crate) struct TcpProtocol { pub(crate) struct UdpProtocol { socket: Arc, remote_addr: SocketAddr, + #[cfg(feature = "metrics")] metrics: Arc, data_in: Mutex>>, } //TODO: PERFORMACE: Use BufWriter and BufReader from std::io! impl TcpProtocol { - pub(crate) fn new(stream: TcpStream, metrics: Arc) -> Self { - Self { stream, metrics } + pub(crate) fn new( + stream: TcpStream, + #[cfg(feature = "metrics")] metrics: Arc, + ) -> Self { + Self { + stream, + #[cfg(feature = "metrics")] + metrics, + } } /// read_except and if it fails, close the protocol - async fn read_except_or_close( + async fn read_or_close( cid: Cid, mut stream: &TcpStream, mut bytes: &mut [u8], w2c_cid_frame_s: &mut mpsc::UnboundedSender<(Cid, Frame)>, - ) { - if let Err(e) = stream.read_exact(&mut bytes).await { - warn!( - ?e, - "closing tcp protocol due to read error, sending close frame to gracefully \ - shutdown" - ); - w2c_cid_frame_s - .send((cid, Frame::Shutdown)) - .await - .expect("Channel or Participant seems no longer to exist to be Shutdown"); + mut end_receiver: &mut Fuse>, + ) -> bool { + match select! { + r = stream.read_exact(&mut bytes).fuse() => Some(r), + _ = end_receiver => None, + } { + Some(Ok(_)) => false, + Some(Err(e)) => { + debug!( + ?cid, + ?e, + "Closing tcp protocol due to read error, sending close frame to gracefully \ + shutdown" + ); + w2c_cid_frame_s + .send((cid, Frame::Shutdown)) + .await + .expect("Channel or Participant seems no longer to exist to be Shutdown"); + true + }, + None => { + trace!(?cid, "shutdown requested"); + true + }, } } @@ -82,60 +103,61 @@ impl TcpProtocol { &self, cid: Cid, w2c_cid_frame_s: &mut mpsc::UnboundedSender<(Cid, Frame)>, - end_receiver: oneshot::Receiver<()>, + end_r: oneshot::Receiver<()>, ) { - trace!("starting up tcp read()"); + trace!("Starting up tcp read()"); + #[cfg(feature = "metrics")] let mut metrics_cache = CidFrameCache::new(self.metrics.frames_wire_in_total.clone(), cid); + #[cfg(feature = "metrics")] let throughput_cache = self .metrics .wire_in_throughput .with_label_values(&[&cid.to_string()]); - let mut stream = self.stream.clone(); - let mut end_receiver = end_receiver.fuse(); + let stream = self.stream.clone(); + let mut end_r = end_r.fuse(); + + macro_rules! read_or_close { + ($x:expr) => { + if TcpProtocol::read_or_close(cid, &stream, $x, w2c_cid_frame_s, &mut end_r).await { + info!("Tcp stream closed, shutting down read"); + break; + } + }; + } loop { - let mut bytes = [0u8; 1]; - let r = select! { - r = stream.read_exact(&mut bytes).fuse() => r, - _ = end_receiver => break, + let frame_no = { + let mut bytes = [0u8; 1]; + read_or_close!(&mut bytes); + bytes[0] }; - if r.is_err() { - info!("tcp stream closed, shutting down read"); - break; - } - let frame_no = bytes[0]; let frame = match frame_no { FRAME_HANDSHAKE => { let mut bytes = [0u8; 19]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; - let magic_number = [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], - ]; + read_or_close!(&mut bytes); + let magic_number = *<&[u8; 7]>::try_from(&bytes[0..7]).unwrap(); Frame::Handshake { magic_number, version: [ - u32::from_le_bytes([bytes[7], bytes[8], bytes[9], bytes[10]]), - u32::from_le_bytes([bytes[11], bytes[12], bytes[13], bytes[14]]), - u32::from_le_bytes([bytes[15], bytes[16], bytes[17], bytes[18]]), + u32::from_le_bytes(*<&[u8; 4]>::try_from(&bytes[7..11]).unwrap()), + u32::from_le_bytes(*<&[u8; 4]>::try_from(&bytes[11..15]).unwrap()), + u32::from_le_bytes(*<&[u8; 4]>::try_from(&bytes[15..19]).unwrap()), ], } }, FRAME_INIT => { let mut bytes = [0u8; 16]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; + read_or_close!(&mut bytes); let pid = Pid::from_le_bytes(bytes); - stream.read_exact(&mut bytes).await.unwrap(); + read_or_close!(&mut bytes); let secret = u128::from_le_bytes(bytes); Frame::Init { pid, secret } }, FRAME_SHUTDOWN => Frame::Shutdown, FRAME_OPEN_STREAM => { let mut bytes = [0u8; 10]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; - let sid = Sid::from_le_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], - bytes[7], - ]); + read_or_close!(&mut bytes); + let sid = Sid::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[0..8]).unwrap()); let prio = bytes[8]; let promises = bytes[9]; Frame::OpenStream { @@ -146,237 +168,155 @@ impl TcpProtocol { }, FRAME_CLOSE_STREAM => { let mut bytes = [0u8; 8]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; - let sid = Sid::from_le_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], - bytes[7], - ]); + read_or_close!(&mut bytes); + let sid = Sid::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[0..8]).unwrap()); Frame::CloseStream { sid } }, FRAME_DATA_HEADER => { let mut bytes = [0u8; 24]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; - let mid = Mid::from_le_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], - bytes[7], - ]); - let sid = Sid::from_le_bytes([ - bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], - bytes[15], - ]); - let length = u64::from_le_bytes([ - bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], - bytes[22], bytes[23], - ]); + read_or_close!(&mut bytes); + let mid = Mid::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[0..8]).unwrap()); + let sid = Sid::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[8..16]).unwrap()); + let length = u64::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[16..24]).unwrap()); Frame::DataHeader { mid, sid, length } }, FRAME_DATA => { let mut bytes = [0u8; 18]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; - let mid = Mid::from_le_bytes([ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], - bytes[7], - ]); - let start = u64::from_le_bytes([ - bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], - bytes[15], - ]); - let length = u16::from_le_bytes([bytes[16], bytes[17]]); - let mut cdata = vec![0; length as usize]; + read_or_close!(&mut bytes); + let mid = Mid::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[0..8]).unwrap()); + let start = u64::from_le_bytes(*<&[u8; 8]>::try_from(&bytes[8..16]).unwrap()); + let length = u16::from_le_bytes(*<&[u8; 2]>::try_from(&bytes[16..18]).unwrap()); + let mut data = vec![0; length as usize]; + #[cfg(feature = "metrics")] throughput_cache.inc_by(length as i64); - Self::read_except_or_close(cid, &stream, &mut cdata, w2c_cid_frame_s).await; - let data = lz4_compress::decompress(&cdata).unwrap(); + read_or_close!(&mut data); Frame::Data { mid, start, data } }, FRAME_RAW => { let mut bytes = [0u8; 2]; - Self::read_except_or_close(cid, &stream, &mut bytes, w2c_cid_frame_s).await; + read_or_close!(&mut bytes); let length = u16::from_le_bytes([bytes[0], bytes[1]]); let mut data = vec![0; length as usize]; - Self::read_except_or_close(cid, &stream, &mut data, w2c_cid_frame_s).await; + read_or_close!(&mut data); Frame::Raw(data) }, - _ => { + other => { // report a RAW frame, but cannot rely on the next 2 bytes to be a size. - // guessing 256 bytes, which might help to sort down issues - let mut data = vec![0; 256]; - Self::read_except_or_close(cid, &stream, &mut data, w2c_cid_frame_s).await; + // guessing 32 bytes, which might help to sort down issues + let mut data = vec![0; 32]; + //keep the first byte! + read_or_close!(&mut data[1..]); + data[0] = other; Frame::Raw(data) }, }; + #[cfg(feature = "metrics")] metrics_cache.with_label_values(&frame).inc(); w2c_cid_frame_s .send((cid, frame)) .await .expect("Channel or Participant seems no longer to exist"); } - trace!("shutting down tcp read()"); + trace!("Shutting down tcp read()"); } /// read_except and if it fails, close the protocol async fn write_or_close( stream: &mut TcpStream, bytes: &[u8], - to_wire_receiver: &mut mpsc::UnboundedReceiver, + c2w_frame_r: &mut mpsc::UnboundedReceiver, ) -> bool { match stream.write_all(&bytes).await { Err(e) => { - warn!( + debug!( ?e, - "got an error writing to tcp, going to close this channel" + "Got an error writing to tcp, going to close this channel" ); - to_wire_receiver.close(); + c2w_frame_r.close(); true }, _ => false, } } - //dezerialize here as this is executed in a seperate thread PER channel. - // Limites Throughput per single Receiver but stays in same thread (maybe as its - // in a threadpool) for TCP, UDP and MPSC pub async fn write_to_wire(&self, cid: Cid, mut c2w_frame_r: mpsc::UnboundedReceiver) { - trace!("starting up tcp write()"); + trace!("Starting up tcp write()"); let mut stream = self.stream.clone(); + #[cfg(feature = "metrics")] let mut metrics_cache = CidFrameCache::new(self.metrics.frames_wire_out_total.clone(), cid); + #[cfg(feature = "metrics")] let throughput_cache = self .metrics .wire_out_throughput .with_label_values(&[&cid.to_string()]); + #[cfg(not(feature = "metrics"))] + let _cid = cid; + + macro_rules! write_or_close { + ($x:expr) => { + if TcpProtocol::write_or_close(&mut stream, $x, &mut c2w_frame_r).await { + info!("Tcp stream closed, shutting down write"); + break; + } + }; + } + while let Some(frame) = c2w_frame_r.next().await { + #[cfg(feature = "metrics")] metrics_cache.with_label_values(&frame).inc(); - if match frame { + match frame { Frame::Handshake { magic_number, version, } => { - Self::write_or_close( - &mut stream, - &FRAME_HANDSHAKE.to_be_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close(&mut stream, &magic_number, &mut c2w_frame_r).await - || Self::write_or_close( - &mut stream, - &version[0].to_le_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close( - &mut stream, - &version[1].to_le_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close( - &mut stream, - &version[2].to_le_bytes(), - &mut c2w_frame_r, - ) - .await + write_or_close!(&FRAME_HANDSHAKE.to_be_bytes()); + write_or_close!(&magic_number); + write_or_close!(&version[0].to_le_bytes()); + write_or_close!(&version[1].to_le_bytes()); + write_or_close!(&version[2].to_le_bytes()); }, Frame::Init { pid, secret } => { - Self::write_or_close(&mut stream, &FRAME_INIT.to_be_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close(&mut stream, &pid.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close( - &mut stream, - &secret.to_le_bytes(), - &mut c2w_frame_r, - ) - .await + write_or_close!(&FRAME_INIT.to_be_bytes()); + write_or_close!(&pid.to_le_bytes()); + write_or_close!(&secret.to_le_bytes()); }, Frame::Shutdown => { - Self::write_or_close( - &mut stream, - &FRAME_SHUTDOWN.to_be_bytes(), - &mut c2w_frame_r, - ) - .await + write_or_close!(&FRAME_SHUTDOWN.to_be_bytes()); }, Frame::OpenStream { sid, prio, promises, } => { - Self::write_or_close( - &mut stream, - &FRAME_OPEN_STREAM.to_be_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close(&mut stream, &sid.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close(&mut stream, &prio.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close( - &mut stream, - &promises.to_le_bytes(), - &mut c2w_frame_r, - ) - .await + write_or_close!(&FRAME_OPEN_STREAM.to_be_bytes()); + write_or_close!(&sid.to_le_bytes()); + write_or_close!(&prio.to_le_bytes()); + write_or_close!(&promises.to_le_bytes()); }, Frame::CloseStream { sid } => { - Self::write_or_close( - &mut stream, - &FRAME_CLOSE_STREAM.to_be_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close(&mut stream, &sid.to_le_bytes(), &mut c2w_frame_r) - .await + write_or_close!(&FRAME_CLOSE_STREAM.to_be_bytes()); + write_or_close!(&sid.to_le_bytes()); }, Frame::DataHeader { mid, sid, length } => { - Self::write_or_close( - &mut stream, - &FRAME_DATA_HEADER.to_be_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close(&mut stream, &mid.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close(&mut stream, &sid.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close( - &mut stream, - &length.to_le_bytes(), - &mut c2w_frame_r, - ) - .await + write_or_close!(&FRAME_DATA_HEADER.to_be_bytes()); + write_or_close!(&mid.to_le_bytes()); + write_or_close!(&sid.to_le_bytes()); + write_or_close!(&length.to_le_bytes()); }, Frame::Data { mid, start, data } => { + #[cfg(feature = "metrics")] throughput_cache.inc_by(data.len() as i64); - let cdata = lz4_compress::compress(&data); - Self::write_or_close(&mut stream, &FRAME_DATA.to_be_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close(&mut stream, &mid.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close(&mut stream, &start.to_le_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close( - &mut stream, - &(cdata.len() as u16).to_le_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close(&mut stream, &cdata, &mut c2w_frame_r).await + write_or_close!(&FRAME_DATA.to_be_bytes()); + write_or_close!(&mid.to_le_bytes()); + write_or_close!(&start.to_le_bytes()); + write_or_close!(&(data.len() as u16).to_le_bytes()); + write_or_close!(&data); }, Frame::Raw(data) => { - Self::write_or_close(&mut stream, &FRAME_RAW.to_be_bytes(), &mut c2w_frame_r) - .await - || Self::write_or_close( - &mut stream, - &(data.len() as u16).to_le_bytes(), - &mut c2w_frame_r, - ) - .await - || Self::write_or_close(&mut stream, &data, &mut c2w_frame_r).await + write_or_close!(&FRAME_RAW.to_be_bytes()); + write_or_close!(&(data.len() as u16).to_le_bytes()); + write_or_close!(&data); }, - } { - //failure - return; } } trace!("shutting down tcp write()"); @@ -387,12 +327,13 @@ impl UdpProtocol { pub(crate) fn new( socket: Arc, remote_addr: SocketAddr, - metrics: Arc, + #[cfg(feature = "metrics")] metrics: Arc, data_in: mpsc::UnboundedReceiver>, ) -> Self { Self { socket, remote_addr, + #[cfg(feature = "metrics")] metrics, data_in: Mutex::new(data_in), } @@ -402,21 +343,23 @@ impl UdpProtocol { &self, cid: Cid, w2c_cid_frame_s: &mut mpsc::UnboundedSender<(Cid, Frame)>, - end_receiver: oneshot::Receiver<()>, + end_r: oneshot::Receiver<()>, ) { - trace!("starting up udp read()"); + trace!("Starting up udp read()"); + #[cfg(feature = "metrics")] let mut metrics_cache = CidFrameCache::new(self.metrics.frames_wire_in_total.clone(), cid); + #[cfg(feature = "metrics")] let throughput_cache = self .metrics .wire_in_throughput .with_label_values(&[&cid.to_string()]); let mut data_in = self.data_in.lock().await; - let mut end_receiver = end_receiver.fuse(); + let mut end_r = end_r.fuse(); while let Some(bytes) = select! { r = data_in.next().fuse() => r, - _ = end_receiver => None, + _ = end_r => None, } { - trace!("got raw UDP message with len: {}", bytes.len()); + trace!("Got raw UDP message with len: {}", bytes.len()); let frame_no = bytes[0]; let frame = match frame_no { FRAME_HANDSHAKE => { @@ -496,6 +439,7 @@ impl UdpProtocol { ]); let length = u16::from_le_bytes([bytes[17], bytes[18]]); let mut data = vec![0; length as usize]; + #[cfg(feature = "metrics")] throughput_cache.inc_by(length as i64); data.copy_from_slice(&bytes[19..]); Frame::Data { mid, start, data } @@ -508,21 +452,27 @@ impl UdpProtocol { }, _ => Frame::Raw(bytes), }; + #[cfg(feature = "metrics")] metrics_cache.with_label_values(&frame).inc(); w2c_cid_frame_s.send((cid, frame)).await.unwrap(); } - trace!("shutting down udp read()"); + trace!("Shutting down udp read()"); } pub async fn write_to_wire(&self, cid: Cid, mut c2w_frame_r: mpsc::UnboundedReceiver) { - trace!("starting up udp write()"); + trace!("Starting up udp write()"); let mut buffer = [0u8; 2000]; + #[cfg(feature = "metrics")] let mut metrics_cache = CidFrameCache::new(self.metrics.frames_wire_out_total.clone(), cid); + #[cfg(feature = "metrics")] let throughput_cache = self .metrics .wire_out_throughput .with_label_values(&[&cid.to_string()]); + #[cfg(not(feature = "metrics"))] + let _cid = cid; while let Some(frame) = c2w_frame_r.next().await { + #[cfg(feature = "metrics")] metrics_cache.with_label_values(&frame).inc(); let len = match frame { Frame::Handshake { @@ -576,6 +526,7 @@ impl UdpProtocol { buffer[9..17].copy_from_slice(&start.to_le_bytes()); buffer[17..19].copy_from_slice(&(data.len() as u16).to_le_bytes()); buffer[19..(data.len() + 19)].clone_from_slice(&data[..]); + #[cfg(feature = "metrics")] throughput_cache.inc_by(data.len() as i64); 19 + data.len() }, @@ -588,7 +539,7 @@ impl UdpProtocol { }; let mut start = 0; while start < len { - trace!(?start, ?len, "splitting up udp frame in multiple packages"); + trace!(?start, ?len, "Splitting up udp frame in multiple packages"); match self .socket .send_to(&buffer[start..len], self.remote_addr) @@ -603,10 +554,115 @@ impl UdpProtocol { ); } }, - Err(e) => error!(?e, "need to handle that error!"), + Err(e) => error!(?e, "Need to handle that error!"), } } } - trace!("shutting down udp write()"); + trace!("Shutting down udp write()"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + metrics::NetworkMetrics, + types::{Cid, Pid}, + }; + use async_std::net; + use futures::{executor::block_on, stream::StreamExt}; + use std::sync::Arc; + + #[test] + fn tcp_read_handshake() { + let pid = Pid::new(); + let cid = 80085; + let metrics = Arc::new(NetworkMetrics::new(&pid).unwrap()); + let addr = std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(127, 0, 0, 1), 50500); + block_on(async { + let server = net::TcpListener::bind(addr).await.unwrap(); + let mut client = net::TcpStream::connect(addr).await.unwrap(); + + let s_stream = server.incoming().next().await.unwrap().unwrap(); + let prot = TcpProtocol::new(s_stream, metrics); + + //Send Handshake + client.write_all(&[FRAME_HANDSHAKE]).await.unwrap(); + client.write_all(b"HELLOWO").await.unwrap(); + client.write_all(&1337u32.to_le_bytes()).await.unwrap(); + client.write_all(&0u32.to_le_bytes()).await.unwrap(); + client.write_all(&42u32.to_le_bytes()).await.unwrap(); + client.flush(); + + //handle data + let (mut w2c_cid_frame_s, mut w2c_cid_frame_r) = mpsc::unbounded::<(Cid, Frame)>(); + let (read_stop_sender, read_stop_receiver) = oneshot::channel(); + let cid2 = cid; + let t = std::thread::spawn(move || { + block_on(async { + prot.read_from_wire(cid2, &mut w2c_cid_frame_s, read_stop_receiver) + .await; + }) + }); + // Assert than we get some value back! Its a Handshake! + //async_std::task::sleep(std::time::Duration::from_millis(1000)); + let (cid_r, frame) = w2c_cid_frame_r.next().await.unwrap(); + assert_eq!(cid, cid_r); + if let Frame::Handshake { + magic_number, + version, + } = frame + { + assert_eq!(&magic_number, b"HELLOWO"); + assert_eq!(version, [1337, 0, 42]); + } else { + panic!("wrong handshake"); + } + read_stop_sender.send(()).unwrap(); + t.join().unwrap(); + }); + } + + #[test] + fn tcp_read_garbage() { + let pid = Pid::new(); + let cid = 80085; + let metrics = Arc::new(NetworkMetrics::new(&pid).unwrap()); + let addr = std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(127, 0, 0, 1), 50501); + block_on(async { + let server = net::TcpListener::bind(addr).await.unwrap(); + let mut client = net::TcpStream::connect(addr).await.unwrap(); + + let s_stream = server.incoming().next().await.unwrap().unwrap(); + let prot = TcpProtocol::new(s_stream, metrics); + + //Send Handshake + client + .write_all("x4hrtzsektfhxugzdtz5r78gzrtzfhxfdthfthuzhfzzufasgasdfg".as_bytes()) + .await + .unwrap(); + client.flush(); + + //handle data + let (mut w2c_cid_frame_s, mut w2c_cid_frame_r) = mpsc::unbounded::<(Cid, Frame)>(); + let (read_stop_sender, read_stop_receiver) = oneshot::channel(); + let cid2 = cid; + let t = std::thread::spawn(move || { + block_on(async { + prot.read_from_wire(cid2, &mut w2c_cid_frame_s, read_stop_receiver) + .await; + }) + }); + // Assert than we get some value back! Its a Raw! + let (cid_r, frame) = w2c_cid_frame_r.next().await.unwrap(); + assert_eq!(cid, cid_r); + if let Frame::Raw(data) = frame { + assert_eq!(&data.as_slice(), b"x4hrtzsektfhxugzdtz5r78gzrtzfhxf"); + } else { + panic!("wrong frame type"); + } + read_stop_sender.send(()).unwrap(); + t.join().unwrap(); + }); } } diff --git a/network/src/scheduler.rs b/network/src/scheduler.rs index 31cd0bd7ba..000ccd92e7 100644 --- a/network/src/scheduler.rs +++ b/network/src/scheduler.rs @@ -1,10 +1,11 @@ +#[cfg(feature = "metrics")] +use crate::metrics::NetworkMetrics; use crate::{ - api::{Address, Participant}, + api::{Participant, ProtocolAddr}, channel::Handshake, - metrics::NetworkMetrics, - participant::BParticipant, + participant::{B2sPrioStatistic, BParticipant, S2bCreateChannel, S2bShutdownBparticipant}, protocols::{Protocols, TcpProtocol, UdpProtocol}, - types::{Cid, Frame, Pid, Sid}, + types::Pid, }; use async_std::{ io, net, @@ -18,6 +19,7 @@ use futures::{ sink::SinkExt, stream::StreamExt, }; +#[cfg(feature = "metrics")] use prometheus::Registry; use rand::Rng; use std::{ @@ -30,16 +32,6 @@ use std::{ use tracing::*; use tracing_futures::Instrument; -#[derive(Debug)] -#[allow(clippy::type_complexity)] -struct ParticipantInfo { - secret: u128, - s2b_create_channel_s: - mpsc::UnboundedSender<(Cid, Sid, Protocols, Vec<(Cid, Frame)>, oneshot::Sender<()>)>, - s2b_shutdown_bparticipant_s: - Option>>>, -} - /// Naming of Channels `x2x` /// - a: api /// - s: scheduler @@ -48,20 +40,32 @@ struct ParticipantInfo { /// - r: protocol /// - w: wire /// - c: channel/handshake + +#[derive(Debug)] +struct ParticipantInfo { + secret: u128, + s2b_create_channel_s: mpsc::UnboundedSender, + s2b_shutdown_bparticipant_s: Option>, +} + +type A2sListen = (ProtocolAddr, oneshot::Sender>); +type A2sConnect = (ProtocolAddr, oneshot::Sender>); +type A2sDisconnect = (Pid, S2bShutdownBparticipant); + #[derive(Debug)] struct ControlChannels { - a2s_listen_r: mpsc::UnboundedReceiver<(Address, oneshot::Sender>)>, - a2s_connect_r: mpsc::UnboundedReceiver<(Address, oneshot::Sender>)>, + a2s_listen_r: mpsc::UnboundedReceiver, + a2s_connect_r: mpsc::UnboundedReceiver, a2s_scheduler_shutdown_r: oneshot::Receiver<()>, - a2s_disconnect_r: mpsc::UnboundedReceiver<(Pid, oneshot::Sender>)>, - b2s_prio_statistic_r: mpsc::UnboundedReceiver<(Pid, u64, u64)>, + a2s_disconnect_r: mpsc::UnboundedReceiver, + b2s_prio_statistic_r: mpsc::UnboundedReceiver, } #[derive(Debug, Clone)] struct ParticipantChannels { s2a_connected_s: mpsc::UnboundedSender, - a2s_disconnect_s: mpsc::UnboundedSender<(Pid, oneshot::Sender>)>, - b2s_prio_statistic_s: mpsc::UnboundedSender<(Pid, u64, u64)>, + a2s_disconnect_s: mpsc::UnboundedSender, + b2s_prio_statistic_s: mpsc::UnboundedSender, } #[derive(Debug)] @@ -74,31 +78,28 @@ pub struct Scheduler { participant_channels: Arc>>, participants: Arc>>, channel_ids: Arc, - channel_listener: RwLock>>, + channel_listener: RwLock>>, + #[cfg(feature = "metrics")] metrics: Arc, } impl Scheduler { - #[allow(clippy::type_complexity)] pub fn new( local_pid: Pid, - registry: Option<&Registry>, + #[cfg(feature = "metrics")] registry: Option<&Registry>, ) -> ( Self, - mpsc::UnboundedSender<(Address, oneshot::Sender>)>, - mpsc::UnboundedSender<(Address, oneshot::Sender>)>, + mpsc::UnboundedSender, + mpsc::UnboundedSender, mpsc::UnboundedReceiver, oneshot::Sender<()>, ) { - let (a2s_listen_s, a2s_listen_r) = - mpsc::unbounded::<(Address, oneshot::Sender>)>(); - let (a2s_connect_s, a2s_connect_r) = - mpsc::unbounded::<(Address, oneshot::Sender>)>(); + let (a2s_listen_s, a2s_listen_r) = mpsc::unbounded::(); + let (a2s_connect_s, a2s_connect_r) = mpsc::unbounded::(); let (s2a_connected_s, s2a_connected_r) = mpsc::unbounded::(); let (a2s_scheduler_shutdown_s, a2s_scheduler_shutdown_r) = oneshot::channel::<()>(); - let (a2s_disconnect_s, a2s_disconnect_r) = - mpsc::unbounded::<(Pid, oneshot::Sender>)>(); - let (b2s_prio_statistic_s, b2s_prio_statistic_r) = mpsc::unbounded::<(Pid, u64, u64)>(); + let (a2s_disconnect_s, a2s_disconnect_r) = mpsc::unbounded::(); + let (b2s_prio_statistic_s, b2s_prio_statistic_r) = mpsc::unbounded::(); let run_channels = Some(ControlChannels { a2s_listen_r, @@ -114,9 +115,14 @@ impl Scheduler { b2s_prio_statistic_s, }; + #[cfg(feature = "metrics")] let metrics = Arc::new(NetworkMetrics::new(&local_pid).unwrap()); - if let Some(registry) = registry { - metrics.register(registry).unwrap(); + + #[cfg(feature = "metrics")] + { + if let Some(registry) = registry { + metrics.register(registry).unwrap(); + } } let mut rng = rand::thread_rng(); @@ -133,6 +139,7 @@ impl Scheduler { participants: Arc::new(RwLock::new(HashMap::new())), channel_ids: Arc::new(AtomicU64::new(0)), channel_listener: RwLock::new(HashMap::new()), + #[cfg(feature = "metrics")] metrics, }, a2s_listen_s, @@ -154,23 +161,21 @@ impl Scheduler { ); } - async fn listen_mgr( - &self, - a2s_listen_r: mpsc::UnboundedReceiver<(Address, oneshot::Sender>)>, - ) { - trace!("start listen_mgr"); + async fn listen_mgr(&self, a2s_listen_r: mpsc::UnboundedReceiver) { + trace!("Start listen_mgr"); a2s_listen_r .for_each_concurrent(None, |(address, s2a_listen_result_s)| { let address = address; async move { - debug!(?address, "got request to open a channel_creator"); + debug!(?address, "Got request to open a channel_creator"); + #[cfg(feature = "metrics")] self.metrics .listen_requests_total .with_label_values(&[match address { - Address::Tcp(_) => "tcp", - Address::Udp(_) => "udp", - Address::Mpsc(_) => "mpsc", + ProtocolAddr::Tcp(_) => "tcp", + ProtocolAddr::Udp(_) => "udp", + ProtocolAddr::Mpsc(_) => "mpsc", }]) .inc(); let (end_sender, end_receiver) = oneshot::channel::<()>(); @@ -183,20 +188,21 @@ impl Scheduler { } }) .await; - trace!("stop listen_mgr"); + trace!("Stop listen_mgr"); } async fn connect_mgr( &self, mut a2s_connect_r: mpsc::UnboundedReceiver<( - Address, + ProtocolAddr, oneshot::Sender>, )>, ) { - trace!("start connect_mgr"); + trace!("Start connect_mgr"); while let Some((addr, pid_sender)) = a2s_connect_r.next().await { let (protocol, handshake) = match addr { - Address::Tcp(addr) => { + ProtocolAddr::Tcp(addr) => { + #[cfg(feature = "metrics")] self.metrics .connect_requests_total .with_label_values(&["tcp"]) @@ -210,11 +216,16 @@ impl Scheduler { }; info!("Connecting Tcp to: {}", stream.peer_addr().unwrap()); ( - Protocols::Tcp(TcpProtocol::new(stream, self.metrics.clone())), + Protocols::Tcp(TcpProtocol::new( + stream, + #[cfg(feature = "metrics")] + self.metrics.clone(), + )), false, ) }, - Address::Udp(addr) => { + ProtocolAddr::Udp(addr) => { + #[cfg(feature = "metrics")] self.metrics .connect_requests_total .with_label_values(&["udp"]) @@ -235,6 +246,7 @@ impl Scheduler { let protocol = UdpProtocol::new( socket.clone(), addr, + #[cfg(feature = "metrics")] self.metrics.clone(), udp_data_receiver, ); @@ -249,24 +261,18 @@ impl Scheduler { self.init_protocol(protocol, Some(pid_sender), handshake) .await; } - trace!("stop connect_mgr"); + trace!("Stop connect_mgr"); } - async fn disconnect_mgr( - &self, - mut a2s_disconnect_r: mpsc::UnboundedReceiver<( - Pid, - oneshot::Sender>, - )>, - ) { - trace!("start disconnect_mgr"); + async fn disconnect_mgr(&self, mut a2s_disconnect_r: mpsc::UnboundedReceiver) { + trace!("Start disconnect_mgr"); while let Some((pid, return_once_successful_shutdown)) = a2s_disconnect_r.next().await { //Closing Participants is done the following way: // 1. We drop our senders and receivers // 2. we need to close BParticipant, this will drop its senderns and receivers // 3. Participant will try to access the BParticipant senders and receivers with // their next api action, it will fail and be closed then. - trace!(?pid, "got request to close participant"); + trace!(?pid, "Got request to close participant"); if let Some(mut pi) = self.participants.write().await.remove(&pid) { let (finished_sender, finished_receiver) = oneshot::channel(); pi.s2b_shutdown_bparticipant_s @@ -278,36 +284,36 @@ impl Scheduler { let e = finished_receiver.await.unwrap(); return_once_successful_shutdown.send(e).unwrap(); } else { - debug!(?pid, "looks like participant is already dropped"); + debug!(?pid, "Looks like participant is already dropped"); return_once_successful_shutdown.send(Ok(())).unwrap(); } - trace!(?pid, "closed participant"); + trace!(?pid, "Closed participant"); } - trace!("stop disconnect_mgr"); + trace!("Stop disconnect_mgr"); } async fn prio_adj_mgr( &self, - mut b2s_prio_statistic_r: mpsc::UnboundedReceiver<(Pid, u64, u64)>, + mut b2s_prio_statistic_r: mpsc::UnboundedReceiver, ) { - trace!("start prio_adj_mgr"); + trace!("Start prio_adj_mgr"); while let Some((_pid, _frame_cnt, _unused)) = b2s_prio_statistic_r.next().await { //TODO adjust prios in participants here! } - trace!("stop prio_adj_mgr"); + trace!("Stop prio_adj_mgr"); } async fn scheduler_shutdown_mgr(&self, a2s_scheduler_shutdown_r: oneshot::Receiver<()>) { - trace!("start scheduler_shutdown_mgr"); + trace!("Start scheduler_shutdown_mgr"); a2s_scheduler_shutdown_r.await.unwrap(); self.closed.store(true, Ordering::Relaxed); - debug!("shutting down all BParticipants gracefully"); + debug!("Shutting down all BParticipants gracefully"); let mut participants = self.participants.write().await; let waitings = participants .drain() .map(|(pid, mut pi)| { - trace!(?pid, "shutting down BParticipants"); + trace!(?pid, "Shutting down BParticipants"); let (finished_sender, finished_receiver) = oneshot::channel(); pi.s2b_shutdown_bparticipant_s .take() @@ -317,33 +323,34 @@ impl Scheduler { (pid, finished_receiver) }) .collect::>(); - debug!("wait for partiticipants to be shut down"); + debug!("Wait for partiticipants to be shut down"); for (pid, recv) in waitings { if let Err(e) = recv.await { error!( ?pid, ?e, - "failed to finish sending all remainding messages to participant when \ + "Failed to finish sending all remainding messages to participant when \ shutting down" ); }; } + debug!("Scheduler shut down gracefully"); //removing the possibility to create new participants, needed to close down // some mgr: self.participant_channels.lock().await.take(); - trace!("stop scheduler_shutdown_mgr"); + trace!("Stop scheduler_shutdown_mgr"); } async fn channel_creator( &self, - addr: Address, + addr: ProtocolAddr, s2s_stop_listening_r: oneshot::Receiver<()>, s2a_listen_result_s: oneshot::Sender>, ) { - trace!(?addr, "start up channel creator"); + trace!(?addr, "Start up channel creator"); match addr { - Address::Tcp(addr) => { + ProtocolAddr::Tcp(addr) => { let listener = match net::TcpListener::bind(addr).await { Ok(listener) => { s2a_listen_result_s.send(Ok(())).unwrap(); @@ -353,27 +360,44 @@ impl Scheduler { info!( ?addr, ?e, - "listener couldn't be started due to error on tcp bind" + "Listener couldn't be started due to error on tcp bind" ); s2a_listen_result_s.send(Err(e)).unwrap(); return; }, }; - trace!(?addr, "listener bound"); + trace!(?addr, "Listener bound"); let mut incoming = listener.incoming(); let mut end_receiver = s2s_stop_listening_r.fuse(); while let Some(stream) = select! { next = incoming.next().fuse() => next, _ = end_receiver => None, } { - let stream = stream.unwrap(); - info!("Accepting Tcp from: {}", stream.peer_addr().unwrap()); - let protocol = TcpProtocol::new(stream, self.metrics.clone()); + let stream = match stream { + Ok(s) => s, + Err(e) => { + warn!(?e, "TcpStream Error, ignoring connection attempt"); + continue; + }, + }; + let peer_addr = match stream.peer_addr() { + Ok(s) => s, + Err(e) => { + warn!(?e, "TcpStream Error, ignoring connection attempt"); + continue; + }, + }; + info!("Accepting Tcp from: {}", peer_addr); + let protocol = TcpProtocol::new( + stream, + #[cfg(feature = "metrics")] + self.metrics.clone(), + ); self.init_protocol(Protocols::Tcp(protocol), None, true) .await; } }, - Address::Udp(addr) => { + ProtocolAddr::Udp(addr) => { let socket = match net::UdpSocket::bind(addr).await { Ok(socket) => { s2a_listen_result_s.send(Ok(())).unwrap(); @@ -383,13 +407,13 @@ impl Scheduler { info!( ?addr, ?e, - "listener couldn't be started due to error on udp bind" + "Listener couldn't be started due to error on udp bind" ); s2a_listen_result_s.send(Err(e)).unwrap(); return; }, }; - trace!(?addr, "listener bound"); + trace!(?addr, "Listener bound"); // receiving is done from here and will be piped to protocol as UDP does not // have any state let mut listeners = HashMap::new(); @@ -412,6 +436,7 @@ impl Scheduler { let protocol = UdpProtocol::new( socket.clone(), remote_addr, + #[cfg(feature = "metrics")] self.metrics.clone(), udp_data_receiver, ); @@ -424,7 +449,7 @@ impl Scheduler { }, _ => unimplemented!(), } - trace!(?addr, "ending channel creator"); + trace!(?addr, "Ending channel creator"); } async fn udp_single_channel_connect( @@ -432,7 +457,7 @@ impl Scheduler { mut w2p_udp_package_s: mpsc::UnboundedSender>, ) { let addr = socket.local_addr(); - trace!(?addr, "start udp_single_channel_connect"); + trace!(?addr, "Start udp_single_channel_connect"); //TODO: implement real closing let (_end_sender, end_receiver) = oneshot::channel::<()>(); @@ -448,7 +473,7 @@ impl Scheduler { datavec.extend_from_slice(&data[0..size]); w2p_udp_package_s.send(datavec).await.unwrap(); } - trace!(?addr, "stop udp_single_channel_connect"); + trace!(?addr, "Stop udp_single_channel_connect"); } async fn init_protocol( @@ -470,6 +495,7 @@ impl Scheduler { // the UDP listening is done in another place. let cid = self.channel_ids.fetch_add(1, Ordering::Relaxed); let participants = self.participants.clone(); + #[cfg(feature = "metrics")] let metrics = self.metrics.clone(); let pool = self.pool.clone(); let local_pid = self.local_pid; @@ -477,11 +503,12 @@ impl Scheduler { // this is necessary for UDP to work at all and to remove code duplication self.pool.spawn_ok( async move { - trace!(?cid, "open channel and be ready for Handshake"); + trace!(?cid, "Open channel and be ready for Handshake"); let handshake = Handshake::new( cid, local_pid, local_secret, + #[cfg(feature = "metrics")] metrics.clone(), send_handshake, ); @@ -490,27 +517,33 @@ impl Scheduler { trace!( ?cid, ?pid, - "detected that my channel is ready!, activating it :)" + "Detected that my channel is ready!, activating it :)" ); let mut participants = participants.write().await; if !participants.contains_key(&pid) { - debug!(?cid, "new participant connected via a channel"); + debug!(?cid, "New participant connected via a channel"); let ( bparticipant, - a2b_steam_open_s, + a2b_stream_open_s, b2a_stream_opened_r, mut s2b_create_channel_s, s2b_shutdown_bparticipant_s, - ) = BParticipant::new(pid, sid, metrics.clone()); + ) = BParticipant::new( + pid, + sid, + #[cfg(feature = "metrics")] + metrics.clone(), + ); let participant = Participant::new( local_pid, pid, - a2b_steam_open_s, + a2b_stream_open_s, b2a_stream_opened_r, participant_channels.a2s_disconnect_s, ); + #[cfg(feature = "metrics")] metrics.participants_connected_total.inc(); participants.insert(pid, ParticipantInfo { secret, @@ -557,7 +590,7 @@ impl Scheduler { ?secret, "Detected incompatible Secret!, this is probably an attack!" ); - error!("just dropping here, TODO handle this correctly!"); + error!("Just dropping here, TODO handle this correctly!"); //TODO if let Some(pid_oneshot) = s2a_return_pid_s { // someone is waiting with `connect`, so give them their Error @@ -571,7 +604,7 @@ impl Scheduler { return; } error!( - "ufff i cant answer the pid_oneshot. as i need to create the SAME \ + "Ufff i cant answer the pid_oneshot. as i need to create the SAME \ participant. maybe switch to ARC" ); } @@ -581,10 +614,11 @@ impl Scheduler { Err(()) => { if let Some(pid_oneshot) = s2a_return_pid_s { // someone is waiting with `connect`, so give them their Error + trace!("returning the Err to api who requested the connect"); pid_oneshot .send(Err(std::io::Error::new( std::io::ErrorKind::PermissionDenied, - "handshake failed, denying connection", + "Handshake failed, denying connection", ))) .unwrap(); } diff --git a/network/src/types.rs b/network/src/types.rs index 46205f25ce..3ede7fd302 100644 --- a/network/src/types.rs +++ b/network/src/types.rs @@ -34,7 +34,7 @@ pub const PROMISES_COMPRESSED: Promises = 8; pub const PROMISES_ENCRYPTED: Promises = 16; pub(crate) const VELOREN_MAGIC_NUMBER: [u8; 7] = [86, 69, 76, 79, 82, 69, 78]; //VELOREN -pub const VELOREN_NETWORK_VERSION: [u32; 3] = [0, 3, 0]; +pub const VELOREN_NETWORK_VERSION: [u32; 3] = [0, 4, 0]; pub(crate) const STREAM_ID_OFFSET1: Sid = Sid::new(0); pub(crate) const STREAM_ID_OFFSET2: Sid = Sid::new(u64::MAX / 2); @@ -90,8 +90,10 @@ pub(crate) enum Frame { } impl Frame { + #[cfg(feature = "metrics")] pub const FRAMES_LEN: u8 = 8; + #[cfg(feature = "metrics")] pub const fn int_to_string(i: u8) -> &'static str { match i { 0 => "Handshake", @@ -106,6 +108,7 @@ impl Frame { } } + #[cfg(feature = "metrics")] pub fn get_int(&self) -> u8 { match self { Frame::Handshake { .. } => 0, @@ -119,6 +122,7 @@ impl Frame { } } + #[cfg(feature = "metrics")] pub fn get_string(&self) -> &str { Self::int_to_string(self.get_int()) } } @@ -130,7 +134,7 @@ impl Pid { /// use veloren_network::{Network, Pid}; /// /// let pid = Pid::new(); - /// let _ = Network::new(pid, None); + /// let _ = Network::new(pid); /// ``` pub fn new() -> Self { Self { diff --git a/network/tests/closing.rs b/network/tests/closing.rs index 25ac05311c..1eb81b1990 100644 --- a/network/tests/closing.rs +++ b/network/tests/closing.rs @@ -1,6 +1,26 @@ +//! How to read those tests: +//! - in the first line we call the helper, this is only debug code. in case +//! you want to have tracing for a special test you set set the bool = true +//! and the sleep to 10000 and your test will start 10 sec delayed with +//! tracing. You need a delay as otherwise the other tests polute your trace +//! - the second line is to simulate a client and a server +//! `network_participant_stream` will return +//! - 2 networks +//! - 2 participants +//! - 2 streams +//! each one `linked` to their counterpart. +//! You see a cryptic use of rust `_` this is because we are testing the +//! `drop` behavior here. +//! - A `_` means this is directly dropped after the line executes, thus +//! immediately executing its `Drop` impl. +//! - A `_p1_a` e.g. means we don't use that Participant yet, but we must +//! not `drop` it yet as we might want to use the Streams. +//! - You sometimes see sleep(1000ms) this is used when we rely on the +//! underlying TCP functionality, as this simulates client and server + use async_std::task; use task::block_on; -use veloren_network::StreamError; +use veloren_network::{Network, ParticipantError, Pid, StreamError, PROMISES_NONE}; mod helper; use helper::{network_participant_stream, tcp}; @@ -19,10 +39,13 @@ fn close_network() { #[test] fn close_participant() { let (_, _) = helper::setup(false, 0); - let (n_a, p1_a, mut s1_a, n_b, p1_b, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, p1_a, mut s1_a, _n_b, p1_b, mut s1_b) = block_on(network_participant_stream(tcp())); - block_on(n_a.disconnect(p1_a)).unwrap(); - block_on(n_b.disconnect(p1_b)).unwrap(); + block_on(p1_a.disconnect()).unwrap(); + assert_eq!( + block_on(p1_b.disconnect()), + Err(ParticipantError::ParticipantDisconnected) + ); assert_eq!(s1_a.send("Hello World"), Err(StreamError::StreamClosed)); assert_eq!( @@ -66,7 +89,7 @@ fn close_streams_in_block_on() { #[test] fn stream_simple_3msg_then_close() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); s1_a.send(1u8).unwrap(); s1_a.send(42).unwrap(); @@ -83,7 +106,7 @@ fn stream_simple_3msg_then_close() { fn stream_send_first_then_receive() { // recv should still be possible even if stream got closed if they are in queue let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); s1_a.send(1u8).unwrap(); s1_a.send(42).unwrap(); @@ -99,7 +122,7 @@ fn stream_send_first_then_receive() { #[test] fn stream_send_1_then_close_stream() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); s1_a.send("this message must be received, even if stream is closed already!") .unwrap(); drop(s1_a); @@ -112,7 +135,7 @@ fn stream_send_1_then_close_stream() { #[test] fn stream_send_100000_then_close_stream() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); for _ in 0..100000 { s1_a.send("woop_PARTY_HARD_woop").unwrap(); } @@ -130,7 +153,7 @@ fn stream_send_100000_then_close_stream() { #[test] fn stream_send_100000_then_close_stream_remote() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, _s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, _s1_b) = block_on(network_participant_stream(tcp())); for _ in 0..100000 { s1_a.send("woop_PARTY_HARD_woop").unwrap(); } @@ -142,7 +165,7 @@ fn stream_send_100000_then_close_stream_remote() { #[test] fn stream_send_100000_then_close_stream_remote2() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, _s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, _s1_b) = block_on(network_participant_stream(tcp())); for _ in 0..100000 { s1_a.send("woop_PARTY_HARD_woop").unwrap(); } @@ -155,7 +178,7 @@ fn stream_send_100000_then_close_stream_remote2() { #[test] fn stream_send_100000_then_close_stream_remote3() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, _s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, _s1_b) = block_on(network_participant_stream(tcp())); for _ in 0..100000 { s1_a.send("woop_PARTY_HARD_woop").unwrap(); } @@ -164,3 +187,137 @@ fn stream_send_100000_then_close_stream_remote3() { drop(s1_a); //no receiving } + +#[test] +fn close_part_then_network() { + let (_, _) = helper::setup(false, 0); + let (n_a, p_a, mut s1_a, _n_b, _p_b, _s1_b) = block_on(network_participant_stream(tcp())); + for _ in 0..1000 { + s1_a.send("woop_PARTY_HARD_woop").unwrap(); + } + drop(p_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); + drop(n_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); +} + +#[test] +fn close_network_then_part() { + let (_, _) = helper::setup(false, 0); + let (n_a, p_a, mut s1_a, _n_b, _p_b, _s1_b) = block_on(network_participant_stream(tcp())); + for _ in 0..1000 { + s1_a.send("woop_PARTY_HARD_woop").unwrap(); + } + drop(n_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); + drop(p_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); +} + +#[test] +fn close_network_then_disconnect_part() { + let (_, _) = helper::setup(false, 0); + let (n_a, p_a, mut s1_a, _n_b, _p_b, _s1_b) = block_on(network_participant_stream(tcp())); + for _ in 0..1000 { + s1_a.send("woop_PARTY_HARD_woop").unwrap(); + } + drop(n_a); + assert!(block_on(p_a.disconnect()).is_err()); + std::thread::sleep(std::time::Duration::from_millis(1000)); +} + +#[test] +fn opened_stream_before_remote_part_is_closed() { + let (_, _) = helper::setup(false, 0); + let (_n_a, p_a, _, _n_b, p_b, _) = block_on(network_participant_stream(tcp())); + let mut s2_a = block_on(p_a.open(10, PROMISES_NONE)).unwrap(); + s2_a.send("HelloWorld").unwrap(); + let mut s2_b = block_on(p_b.opened()).unwrap(); + drop(p_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); + assert_eq!(block_on(s2_b.recv()), Ok("HelloWorld".to_string())); +} + +#[test] +fn opened_stream_after_remote_part_is_closed() { + let (_, _) = helper::setup(false, 0); + let (_n_a, p_a, _, _n_b, p_b, _) = block_on(network_participant_stream(tcp())); + let mut s2_a = block_on(p_a.open(10, PROMISES_NONE)).unwrap(); + s2_a.send("HelloWorld").unwrap(); + drop(p_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); + let mut s2_b = block_on(p_b.opened()).unwrap(); + assert_eq!(block_on(s2_b.recv()), Ok("HelloWorld".to_string())); + assert_eq!( + block_on(p_b.opened()).unwrap_err(), + ParticipantError::ParticipantDisconnected + ); +} + +#[test] +fn open_stream_after_remote_part_is_closed() { + let (_, _) = helper::setup(false, 0); + let (_n_a, p_a, _, _n_b, p_b, _) = block_on(network_participant_stream(tcp())); + let mut s2_a = block_on(p_a.open(10, PROMISES_NONE)).unwrap(); + s2_a.send("HelloWorld").unwrap(); + drop(p_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); + let mut s2_b = block_on(p_b.opened()).unwrap(); + assert_eq!(block_on(s2_b.recv()), Ok("HelloWorld".to_string())); + assert_eq!( + block_on(p_b.open(20, PROMISES_NONE)).unwrap_err(), + ParticipantError::ParticipantDisconnected + ); +} + +#[test] +fn failed_stream_open_after_remote_part_is_closed() { + let (_, _) = helper::setup(false, 0); + let (_n_a, p_a, _, _n_b, p_b, _) = block_on(network_participant_stream(tcp())); + drop(p_a); + std::thread::sleep(std::time::Duration::from_millis(1000)); + assert_eq!( + block_on(p_b.opened()).unwrap_err(), + ParticipantError::ParticipantDisconnected + ); +} + +#[test] +fn open_participant_before_remote_part_is_closed() { + let (n_a, f) = Network::new(Pid::fake(1)); + std::thread::spawn(f); + let (n_b, f) = Network::new(Pid::fake(2)); + std::thread::spawn(f); + let addr = tcp(); + block_on(n_a.listen(addr.clone())).unwrap(); + let p_b = block_on(n_b.connect(addr)).unwrap(); + let mut s1_b = block_on(p_b.open(10, PROMISES_NONE)).unwrap(); + s1_b.send("HelloWorld").unwrap(); + let p_a = block_on(n_a.connected()).unwrap(); + drop(s1_b); + drop(p_b); + drop(n_b); + std::thread::sleep(std::time::Duration::from_millis(1000)); + let mut s1_a = block_on(p_a.opened()).unwrap(); + assert_eq!(block_on(s1_a.recv()), Ok("HelloWorld".to_string())); +} + +#[test] +fn open_participant_after_remote_part_is_closed() { + let (n_a, f) = Network::new(Pid::fake(1)); + std::thread::spawn(f); + let (n_b, f) = Network::new(Pid::fake(2)); + std::thread::spawn(f); + let addr = tcp(); + block_on(n_a.listen(addr.clone())).unwrap(); + let p_b = block_on(n_b.connect(addr)).unwrap(); + let mut s1_b = block_on(p_b.open(10, PROMISES_NONE)).unwrap(); + s1_b.send("HelloWorld").unwrap(); + drop(s1_b); + drop(p_b); + drop(n_b); + std::thread::sleep(std::time::Duration::from_millis(1000)); + let p_a = block_on(n_a.connected()).unwrap(); + let mut s1_a = block_on(p_a.opened()).unwrap(); + assert_eq!(block_on(s1_a.recv()), Ok("HelloWorld".to_string())); +} diff --git a/network/tests/helper.rs b/network/tests/helper.rs index f043074e8e..53ce8e0c47 100644 --- a/network/tests/helper.rs +++ b/network/tests/helper.rs @@ -1,16 +1,13 @@ use lazy_static::*; use std::{ net::SocketAddr, - sync::{ - atomic::{AtomicU16, Ordering}, - Arc, - }, + sync::atomic::{AtomicU16, Ordering}, thread, time::Duration, }; use tracing::*; use tracing_subscriber::EnvFilter; -use veloren_network::{Address, Network, Participant, Pid, Stream, PROMISES_NONE}; +use veloren_network::{Network, Participant, Pid, ProtocolAddr, Stream, PROMISES_NONE}; #[allow(dead_code)] pub fn setup(tracing: bool, mut sleep: u64) -> (u64, u64) { @@ -50,18 +47,11 @@ pub fn setup(tracing: bool, mut sleep: u64) -> (u64, u64) { #[allow(dead_code)] pub async fn network_participant_stream( - addr: Address, -) -> ( - Network, - Arc, - Stream, - Network, - Arc, - Stream, -) { - let (n_a, f_a) = Network::new(Pid::fake(1), None); + addr: ProtocolAddr, +) -> (Network, Participant, Stream, Network, Participant, Stream) { + let (n_a, f_a) = Network::new(Pid::fake(1)); std::thread::spawn(f_a); - let (n_b, f_b) = Network::new(Pid::fake(2), None); + let (n_b, f_b) = Network::new(Pid::fake(2)); std::thread::spawn(f_b); n_a.listen(addr.clone()).await.unwrap(); @@ -75,19 +65,19 @@ pub async fn network_participant_stream( } #[allow(dead_code)] -pub fn tcp() -> veloren_network::Address { +pub fn tcp() -> veloren_network::ProtocolAddr { lazy_static! { static ref PORTS: AtomicU16 = AtomicU16::new(5000); } let port = PORTS.fetch_add(1, Ordering::Relaxed); - veloren_network::Address::Tcp(SocketAddr::from(([127, 0, 0, 1], port))) + veloren_network::ProtocolAddr::Tcp(SocketAddr::from(([127, 0, 0, 1], port))) } #[allow(dead_code)] -pub fn udp() -> veloren_network::Address { +pub fn udp() -> veloren_network::ProtocolAddr { lazy_static! { static ref PORTS: AtomicU16 = AtomicU16::new(5000); } let port = PORTS.fetch_add(1, Ordering::Relaxed); - veloren_network::Address::Udp(SocketAddr::from(([127, 0, 0, 1], port))) + veloren_network::ProtocolAddr::Udp(SocketAddr::from(([127, 0, 0, 1], port))) } diff --git a/network/tests/integration.rs b/network/tests/integration.rs index 6fe0dea42a..7f37d5e78e 100644 --- a/network/tests/integration.rs +++ b/network/tests/integration.rs @@ -4,7 +4,7 @@ use veloren_network::{NetworkError, StreamError}; mod helper; use helper::{network_participant_stream, tcp, udp}; use std::io::ErrorKind; -use veloren_network::{Address, Network, Pid, PROMISES_CONSISTENCY, PROMISES_ORDERED}; +use veloren_network::{Network, Pid, ProtocolAddr, PROMISES_CONSISTENCY, PROMISES_ORDERED}; #[test] #[ignore] @@ -17,7 +17,7 @@ fn network_20s() { #[test] fn stream_simple() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); s1_a.send("Hello World").unwrap(); assert_eq!(block_on(s1_b.recv()), Ok("Hello World".to_string())); @@ -26,7 +26,7 @@ fn stream_simple() { #[test] fn stream_simple_3msg() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); s1_a.send("Hello World").unwrap(); s1_a.send(1337).unwrap(); @@ -39,7 +39,7 @@ fn stream_simple_3msg() { #[test] fn stream_simple_udp() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(udp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(udp())); s1_a.send("Hello World").unwrap(); assert_eq!(block_on(s1_b.recv()), Ok("Hello World".to_string())); @@ -48,7 +48,7 @@ fn stream_simple_udp() { #[test] fn stream_simple_udp_3msg() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(udp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(udp())); s1_a.send("Hello World").unwrap(); s1_a.send(1337).unwrap(); @@ -62,24 +62,24 @@ fn stream_simple_udp_3msg() { #[ignore] fn tcp_and_udp_2_connections() -> std::result::Result<(), Box> { let (_, _) = helper::setup(false, 0); - let (network, f) = Network::new(Pid::new(), None); - let (remote, fr) = Network::new(Pid::new(), None); + let (network, f) = Network::new(Pid::new()); + let (remote, fr) = Network::new(Pid::new()); std::thread::spawn(f); std::thread::spawn(fr); block_on(async { remote - .listen(Address::Tcp("0.0.0.0:2000".parse().unwrap())) + .listen(ProtocolAddr::Tcp("0.0.0.0:2000".parse().unwrap())) .await?; remote - .listen(Address::Udp("0.0.0.0:2001".parse().unwrap())) + .listen(ProtocolAddr::Udp("0.0.0.0:2001".parse().unwrap())) .await?; let p1 = network - .connect(Address::Tcp("127.0.0.1:2000".parse().unwrap())) + .connect(ProtocolAddr::Tcp("127.0.0.1:2000".parse().unwrap())) .await?; let p2 = network - .connect(Address::Udp("127.0.0.1:2001".parse().unwrap())) + .connect(ProtocolAddr::Udp("127.0.0.1:2001".parse().unwrap())) .await?; - assert!(std::sync::Arc::ptr_eq(&p1, &p2)); + assert_eq!(&p1, &p2); Ok(()) }) } @@ -87,7 +87,7 @@ fn tcp_and_udp_2_connections() -> std::result::Result<(), Box std::result::Result<(), Box> { let (_, _) = helper::setup(false, 0); - let (network, f) = Network::new(Pid::new(), None); + let (network, f) = Network::new(Pid::new()); std::thread::spawn(f); let udp1 = udp(); let tcp1 = tcp(); @@ -95,7 +95,7 @@ fn failed_listen_on_used_ports() -> std::result::Result<(), Box std::result::Result<(), Box> let (_, _) = helper::setup(false, 0); // Create a Network, listen on Port `1200` and wait for a Stream to be opened, // then answer `Hello World` - let (network, f) = Network::new(Pid::new(), None); - let (remote, fr) = Network::new(Pid::new(), None); + let (network, f) = Network::new(Pid::new()); + let (remote, fr) = Network::new(Pid::new()); std::thread::spawn(f); std::thread::spawn(fr); block_on(async { network - .listen(Address::Tcp("127.0.0.1:1200".parse().unwrap())) + .listen(ProtocolAddr::Tcp("127.0.0.1:1200".parse().unwrap())) .await?; let remote_p = remote - .connect(Address::Tcp("127.0.0.1:1200".parse().unwrap())) + .connect(ProtocolAddr::Tcp("127.0.0.1:1200".parse().unwrap())) .await?; // keep it alive let _stream_p = remote_p @@ -148,16 +148,16 @@ fn api_stream_recv_main() -> std::result::Result<(), Box> let (_, _) = helper::setup(false, 0); // Create a Network, listen on Port `1220` and wait for a Stream to be opened, // then listen on it - let (network, f) = Network::new(Pid::new(), None); - let (remote, fr) = Network::new(Pid::new(), None); + let (network, f) = Network::new(Pid::new()); + let (remote, fr) = Network::new(Pid::new()); std::thread::spawn(f); std::thread::spawn(fr); block_on(async { network - .listen(Address::Tcp("127.0.0.1:1220".parse().unwrap())) + .listen(ProtocolAddr::Tcp("127.0.0.1:1220".parse().unwrap())) .await?; let remote_p = remote - .connect(Address::Tcp("127.0.0.1:1220".parse().unwrap())) + .connect(ProtocolAddr::Tcp("127.0.0.1:1220".parse().unwrap())) .await?; let mut stream_p = remote_p .open(16, PROMISES_ORDERED | PROMISES_CONSISTENCY) @@ -174,7 +174,7 @@ fn api_stream_recv_main() -> std::result::Result<(), Box> #[test] fn wrong_parse() { let (_, _) = helper::setup(false, 0); - let (_n_a, _, mut s1_a, _n_b, _, mut s1_b) = block_on(network_participant_stream(tcp())); + let (_n_a, _p_a, mut s1_a, _n_b, _p_b, mut s1_b) = block_on(network_participant_stream(tcp())); s1_a.send(1337).unwrap(); match block_on(s1_b.recv::()) { diff --git a/nix/.envrc b/nix/.envrc new file mode 100644 index 0000000000..1d953f4bd7 --- /dev/null +++ b/nix/.envrc @@ -0,0 +1 @@ +use nix diff --git a/nix/Cargo.nix b/nix/Cargo.nix index bc02b0d6ac..73e490f88a 100644 --- a/nix/Cargo.nix +++ b/nix/Cargo.nix @@ -180,24 +180,7 @@ rec { authors = [ "Remi Rampin " ]; }; - "ahash 0.2.18" = rec { - crateName = "ahash"; - version = "0.2.18"; - edition = "2018"; - sha256 = "1lxmn0igyizs10fasgjr4mki97wa4d7ijygjvk0lc28jiw0vacvg"; - authors = [ "Tom Kaitchuck " ]; - dependencies = [{ - name = "const-random"; - packageId = "const-random"; - optional = true; - }]; - features = { - "compile-time-rng" = [ "const-random" ]; - "default" = [ "compile-time-rng" ]; - }; - resolvedDefaultFeatures = [ "compile-time-rng" "const-random" ]; - }; - "ahash 0.3.8" = rec { + "ahash" = rec { crateName = "ahash"; version = "0.3.8"; edition = "2018"; @@ -312,15 +295,6 @@ rec { }]; }; - "anyhow" = rec { - crateName = "anyhow"; - version = "1.0.31"; - edition = "2018"; - sha256 = "0pqrpvlaicjpaf5zfqgwq1rg39gfvqhs9sz6a1acm5zc13671fw5"; - authors = [ "David Tolnay " ]; - features = { "default" = [ "std" ]; }; - resolvedDefaultFeatures = [ "default" "std" ]; - }; "anymap" = rec { crateName = "anymap"; version = "0.12.1"; @@ -378,7 +352,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "full" ]; } ]; @@ -407,7 +381,6 @@ rec { "default" = [ "std" ]; "serde-1" = [ "serde" ]; }; - resolvedDefaultFeatures = [ "default" "std" ]; }; "arrayvec 0.5.1" = rec { crateName = "arrayvec"; @@ -416,6 +389,7 @@ rec { sha256 = "1f5mca8kiiwhvhxd1mbnq68j6v6rk139sch567zwwzl6hs37vxyg"; authors = [ "bluss" ]; features = { "default" = [ "std" ]; }; + resolvedDefaultFeatures = [ "default" "std" ]; }; "ascii" = rec { crateName = "ascii"; @@ -1334,24 +1308,6 @@ rec { } ]; - }; - "chashmap" = rec { - crateName = "chashmap"; - version = "2.2.2"; - edition = "2015"; - sha256 = "0igsvpc2ajd6w68w4dwn0fln6yww8gq4pq9x02wj36g3q71a6hgz"; - authors = [ "ticki " ]; - dependencies = [ - { - name = "owning_ref"; - packageId = "owning_ref"; - } - { - name = "parking_lot"; - packageId = "parking_lot 0.4.8"; - } - ]; - }; "chrono" = rec { crateName = "chrono"; @@ -1637,31 +1593,6 @@ rec { ]; }; - "colored" = rec { - crateName = "colored"; - version = "1.9.3"; - edition = "2015"; - sha256 = "0nbc1czs512h1k696y7glv1kjrb2b914zpxraic6q5fgv80wizzl"; - authors = [ "Thomas Wickham " ]; - dependencies = [ - { - name = "atty"; - packageId = "atty"; - } - { - name = "lazy_static"; - packageId = "lazy_static"; - } - { - name = "winapi"; - packageId = "winapi 0.3.8"; - usesDefaultFeatures = false; - target = { target, features }: target."windows"; - features = [ "consoleapi" "processenv" "winbase" ]; - } - ]; - features = { }; - }; "conrod_core" = rec { crateName = "conrod_core"; version = "0.63.0"; @@ -1800,15 +1731,11 @@ rec { }; "const-tweaker" = rec { crateName = "const-tweaker"; - version = "0.2.6"; + version = "0.3.1"; edition = "2018"; - sha256 = "0l7jz1rdxixs2d7dpbywsqzj7z0b1a8qdd5da7a9dn6c5wfkxgkz"; + sha256 = "0xp2wxfbksbnra7pbgcm3nk34g28782qsgdhgc66hvlvqgnf4yr9"; authors = [ "Thomas Versteeg " ]; dependencies = [ - { - name = "anyhow"; - packageId = "anyhow"; - } { name = "async-std"; packageId = "async-std"; @@ -1817,6 +1744,10 @@ rec { name = "const-tweaker-attribute"; packageId = "const-tweaker-attribute"; } + { + name = "ctor"; + packageId = "ctor"; + } { name = "dashmap"; packageId = "dashmap"; @@ -1843,9 +1774,9 @@ rec { }; "const-tweaker-attribute" = rec { crateName = "const-tweaker-attribute"; - version = "0.4.1"; + version = "0.5.0"; edition = "2018"; - sha256 = "1fpi3h7qmhr8qpdfdi93l0s630l4zfadwx62r14vjfxxzs7x4hqs"; + sha256 = "1n60l09hb0h2hiz2csvvbn3az0qzmaqfyfhbih60q47ny0w9ww1d"; procMacro = true; authors = [ "Thomas Versteeg " ]; dependencies = [ @@ -1863,7 +1794,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; } ]; @@ -2083,9 +2014,9 @@ rec { }; "cpal" = rec { crateName = "cpal"; - version = "0.10.0"; + version = "0.11.0"; edition = "2015"; - sha256 = "084142l0k28x2f3v8raw1rg3kbmqf15a9qz1b5dhp1dy9410gv9x"; + sha256 = "0nfx8d227pcfgb81a3y7418xzbp066s08g5xjlmgc0zld5fxambb"; authors = [ "The CPAL contributors" "Pierre Krieger " @@ -2112,10 +2043,6 @@ rec { ((target."os" == "macos") || (target."os" == "ios")); features = [ "audio_unit" "core_audio" ]; } - { - name = "failure"; - packageId = "failure"; - } { name = "lazy_static"; packageId = "lazy_static"; @@ -2137,6 +2064,10 @@ rec { usesDefaultFeatures = false; target = { target, features }: (target."os" == "emscripten"); } + { + name = "thiserror"; + packageId = "thiserror"; + } { name = "winapi"; packageId = "winapi 0.3.8"; @@ -2154,6 +2085,7 @@ rec { "objbase" "std" "synchapi" + "winbase" "winuser" ]; } @@ -2209,7 +2141,7 @@ rec { } { name = "itertools"; - packageId = "itertools 0.9.0"; + packageId = "itertools"; } { name = "lazy_static"; @@ -2280,7 +2212,7 @@ rec { } { name = "itertools"; - packageId = "itertools 0.9.0"; + packageId = "itertools"; } ]; @@ -2625,6 +2557,27 @@ rec { features = { "libc" = [ "memchr/libc" ]; }; resolvedDefaultFeatures = [ "default" ]; }; + "ctor" = rec { + crateName = "ctor"; + version = "0.1.15"; + edition = "2018"; + sha256 = "09x2my9x33srjdip8yf4lm5gq7xqis2694abvpa64r60pajqm19r"; + procMacro = true; + authors = [ "Matt Mastracci " ]; + dependencies = [ + { + name = "quote"; + packageId = "quote 1.0.7"; + } + { + name = "syn"; + packageId = "syn 1.0.33"; + usesDefaultFeatures = false; + features = [ "full" "parsing" "printing" "proc-macro" ]; + } + ]; + + }; "daggy" = rec { crateName = "daggy"; version = "0.5.0"; @@ -2691,7 +2644,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "full" "extra-traits" ]; } ]; @@ -2716,7 +2669,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; } ]; @@ -2730,7 +2683,7 @@ rec { dependencies = [ { name = "ahash"; - packageId = "ahash 0.3.8"; + packageId = "ahash"; } { name = "cfg-if"; @@ -2774,30 +2727,6 @@ rec { ]; features = { "gzip" = [ "gzip-header" ]; }; }; - "derivative" = rec { - crateName = "derivative"; - version = "1.0.4"; - edition = "2015"; - sha256 = "0dbiw51q4b33gwhkicai06xym0hb6fkid9xn24h3x2k68qsqhv9w"; - procMacro = true; - authors = [ "mcarton " ]; - dependencies = [ - { - name = "proc-macro2"; - packageId = "proc-macro2 0.4.30"; - } - { - name = "quote"; - packageId = "quote 0.6.13"; - } - { - name = "syn"; - packageId = "syn 0.15.44"; - features = [ "visit" "extra-traits" ]; - } - ]; - features = { "test-nightly" = [ "trybuild" ]; }; - }; "deunicode" = rec { crateName = "deunicode"; version = "1.1.1"; @@ -2883,7 +2812,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "full" "fold" ]; } ]; @@ -2909,30 +2838,30 @@ rec { features = { }; resolvedDefaultFeatures = [ "default" ]; }; - "directories" = rec { - crateName = "directories"; - version = "2.0.2"; - edition = "2015"; - sha256 = "071pjx760m0dccdxlhwsz9m0zl180hxwlag62bydfl54fa0pf6jm"; - authors = [ "Simon Ochsenreither " ]; + "directories-next" = rec { + crateName = "directories-next"; + version = "1.0.1"; + edition = "2018"; + sha256 = "0j2sb1rmhn2lkfacq1qasa1rl7c8cv2iff1qqwnpxv2vji7a1si1"; + authors = [ "The @xdg-rs members" ]; dependencies = [ { name = "cfg-if"; packageId = "cfg-if"; } { - name = "dirs-sys"; - packageId = "dirs-sys"; + name = "dirs-sys-next"; + packageId = "dirs-sys-next"; } ]; }; - "dirs-sys" = rec { - crateName = "dirs-sys"; - version = "0.3.5"; + "dirs-sys-next" = rec { + crateName = "dirs-sys-next"; + version = "0.1.0"; edition = "2015"; - sha256 = "0ym5843xack45b1yjahrh3q2f72shnwf1dd2jncf9qsxf3sxg4wf"; - authors = [ "Simon Ochsenreither " ]; + sha256 = "16iw13jarlagihbiq3n5sd8wfl7vpra089i3h8a2cfcmm2wgfq4w"; + authors = [ "The @xdg-rs members" ]; dependencies = [ { name = "libc"; @@ -3055,43 +2984,6 @@ rec { features = { "default" = [ "use_std" ]; }; resolvedDefaultFeatures = [ "default" "use_std" ]; }; - "env_logger" = rec { - crateName = "env_logger"; - version = "0.6.2"; - edition = "2015"; - sha256 = "1lx2s5nk96xx4i3m4zc4ghqgi8kb07dsnyiv8jk2clhax42dxz5a"; - authors = [ "The Rust Project Developers" ]; - dependencies = [ - { - name = "atty"; - packageId = "atty"; - optional = true; - } - { - name = "humantime"; - packageId = "humantime"; - optional = true; - } - { - name = "log"; - packageId = "log"; - features = [ "std" ]; - } - { - name = "regex"; - packageId = "regex"; - optional = true; - } - { - name = "termcolor"; - packageId = "termcolor"; - optional = true; - } - ]; - features = { "default" = [ "termcolor" "atty" "humantime" "regex" ]; }; - resolvedDefaultFeatures = - [ "atty" "default" "humantime" "regex" "termcolor" ]; - }; "error-chain" = rec { crateName = "error-chain"; version = "0.12.2"; @@ -3233,7 +3125,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; } { name = "synstructure"; @@ -3242,35 +3134,6 @@ rec { ]; features = { }; }; - "fern" = rec { - crateName = "fern"; - version = "0.5.9"; - edition = "2018"; - sha256 = "1anslk0hx9an4ypcaxqff080hgbcxm7ji7d4qf4f6qx1mkav16p6"; - authors = [ "David Ross " ]; - dependencies = [ - { - name = "chrono"; - packageId = "chrono"; - } - { - name = "colored"; - packageId = "colored"; - optional = true; - } - { - name = "log"; - packageId = "log"; - features = [ "std" ]; - } - ]; - features = { - "reopen-03" = [ "reopen" "libc" ]; - "syslog-3" = [ "syslog3" ]; - "syslog-4" = [ "syslog4" ]; - }; - resolvedDefaultFeatures = [ "colored" ]; - }; "filetime" = rec { crateName = "filetime"; version = "0.2.10"; @@ -3628,7 +3491,7 @@ rec { "std" = [ "futures-core/std" "futures-task/std" "futures-util/std" ]; "thread-pool" = [ "std" "num_cpus" ]; }; - resolvedDefaultFeatures = [ "num_cpus" "std" "thread-pool" ]; + resolvedDefaultFeatures = [ "default" "num_cpus" "std" "thread-pool" ]; }; "futures-io" = rec { crateName = "futures-io"; @@ -3664,7 +3527,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "full" ]; } ]; @@ -4448,9 +4311,9 @@ rec { }; "git2" = rec { crateName = "git2"; - version = "0.10.2"; + version = "0.13.6"; edition = "2018"; - sha256 = "09ks0gsg14s6h65bzrrsl7vn0n7jmj7ffk2syim621m9m0gga6kw"; + sha256 = "07nkyk407bbcyrwqmvn855bnab50nk5yjws2pz253rw0544b5r0i"; authors = [ "Josh Triplett " "Alex Crichton " @@ -5173,14 +5036,14 @@ rec { }; "hashbrown" = rec { crateName = "hashbrown"; - version = "0.6.3"; + version = "0.7.2"; edition = "2018"; - sha256 = "1bbf9k46v57zi41m6hjwn83rjldyipv5zwxmdsa7a9c1rb876q4f"; + sha256 = "1ks110dbp81ddn3v826vnrlk5psh3vgvwf4rmb9s0gfdpyb2wa4n"; authors = [ "Amanieu d'Antras " ]; dependencies = [ { name = "ahash"; - packageId = "ahash 0.2.18"; + packageId = "ahash"; optional = true; usesDefaultFeatures = false; } @@ -5198,7 +5061,7 @@ rec { ]; buildDependencies = [{ name = "autocfg"; - packageId = "autocfg 0.1.7"; + packageId = "autocfg 1.0.0"; }]; devDependencies = [{ name = "rayon"; @@ -5206,7 +5069,7 @@ rec { }]; features = { "ahash-compile-time-rng" = [ "ahash/compile-time-rng" ]; - "default" = [ "ahash" "ahash-compile-time-rng" "inline-more" ]; + "default" = [ "ahash" "inline-more" ]; "rustc-dep-of-std" = [ "nightly" "core" @@ -5215,15 +5078,8 @@ rec { "rustc-internal-api" ]; }; - resolvedDefaultFeatures = [ - "ahash" - "ahash-compile-time-rng" - "default" - "inline-more" - "nightly" - "rayon" - "serde" - ]; + resolvedDefaultFeatures = + [ "ahash" "default" "inline-more" "nightly" "rayon" "serde" ]; }; "hermit-abi" = rec { crateName = "hermit-abi"; @@ -5426,18 +5282,6 @@ rec { features = { "default" = [ "std" ]; }; resolvedDefaultFeatures = [ "default" "std" ]; }; - "humantime" = rec { - crateName = "humantime"; - version = "1.3.0"; - edition = "2015"; - sha256 = "0krwgbf35pd46xvkqg14j070vircsndabahahlv3rwhflpy4q06z"; - authors = [ "Paul Colomiets " ]; - dependencies = [{ - name = "quick-error"; - packageId = "quick-error"; - }]; - - }; "hyper" = rec { crateName = "hyper"; version = "0.12.35"; @@ -5794,21 +5638,7 @@ rec { }]; }; - "itertools 0.8.2" = rec { - crateName = "itertools"; - version = "0.8.2"; - edition = "2015"; - sha256 = "1154j48aw913v5jnyhpxialxhdn2sfpl4d7bwididyb1r05jsspm"; - authors = [ "bluss" ]; - dependencies = [{ - name = "either"; - packageId = "either"; - usesDefaultFeatures = false; - }]; - features = { "default" = [ "use_std" ]; }; - resolvedDefaultFeatures = [ "default" "use_std" ]; - }; - "itertools 0.9.0" = rec { + "itertools" = rec { crateName = "itertools"; version = "0.9.0"; edition = "2018"; @@ -5920,10 +5750,10 @@ rec { }; "lewton" = rec { crateName = "lewton"; - version = "0.9.4"; + version = "0.10.1"; edition = "2015"; - sha256 = "1l4bc88cpr8p94dfycykn8gajg20kp611kx159fc8dkh64d2qm4d"; - type = [ "lib" "cdylib" ]; + sha256 = "03bwszwdra225y8i6061m799pzy1kg130mr13vma0jqzjykvwhmy"; + type = [ "lib" "staticlib" ]; authors = [ "est31 " ]; dependencies = [ { @@ -5936,8 +5766,9 @@ rec { optional = true; } { - name = "smallvec"; - packageId = "smallvec 0.6.13"; + name = "tinyvec"; + packageId = "tinyvec"; + features = [ "alloc" ]; } ]; devDependencies = [{ @@ -5965,9 +5796,9 @@ rec { }; "libgit2-sys" = rec { crateName = "libgit2-sys"; - version = "0.9.2"; + version = "0.12.7+1.0.0"; edition = "2018"; - sha256 = "05jayrq6km6fvh65grqr2i9mikvfvwfjrlhc2n1zngh6ys0wfw28"; + sha256 = "1q2k647p267cam1chmyzrz676xnjlhyfbzgca69ppjwvcil7kl5w"; libName = "libgit2_sys"; libPath = "lib.rs"; authors = [ @@ -6047,10 +5878,10 @@ rec { }; "libsqlite3-sys" = rec { crateName = "libsqlite3-sys"; - version = "0.9.3"; - edition = "2015"; - sha256 = "1a2wjlf4xn97v4mbwf20wxb54xd863m0cbddb0j1s251j7yiswfk"; - authors = [ "John Gallagher " ]; + version = "0.18.0"; + edition = "2018"; + sha256 = "1ggpbnis0rci97ln628y2v6pkgfhb6zgc8rsp444mkdfph14lw0y"; + authors = [ "The rusqlite developers" ]; buildDependencies = [ { name = "cc"; @@ -6071,18 +5902,18 @@ rec { ]; features = { "buildtime_bindgen" = [ "bindgen" "pkg-config" "vcpkg" ]; - "bundled" = [ "cc" ]; + "bundled" = [ "cc" "bundled_bindings" ]; + "bundled-windows" = [ "cc" "bundled_bindings" ]; "default" = [ "min_sqlite_version_3_6_8" ]; - "min_sqlite_version_3_6_11" = [ "pkg-config" "vcpkg" ]; "min_sqlite_version_3_6_23" = [ "pkg-config" "vcpkg" ]; "min_sqlite_version_3_6_8" = [ "pkg-config" "vcpkg" ]; "min_sqlite_version_3_7_16" = [ "pkg-config" "vcpkg" ]; - "min_sqlite_version_3_7_3" = [ "pkg-config" "vcpkg" ]; - "min_sqlite_version_3_7_4" = [ "pkg-config" "vcpkg" ]; "min_sqlite_version_3_7_7" = [ "pkg-config" "vcpkg" ]; + "session" = [ "preupdate_hook" ]; }; resolvedDefaultFeatures = [ "bundled" + "bundled_bindings" "cc" "default" "min_sqlite_version_3_6_8" @@ -6399,7 +6230,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "extra-traits" ]; } ]; @@ -6847,9 +6678,9 @@ rec { }; "notify" = rec { crateName = "notify"; - version = "5.0.0-pre.2"; + version = "5.0.0-pre.3"; edition = "2018"; - sha256 = "1hvkssakq5iac8aizhq2xynr9srg0sdy20n3k1azpgw8a6vc003v"; + sha256 = "03kxlcnpg8xkhp3g04y8basy7kpkzwjx97hfp9hb3d48rw3kdl3p"; authors = [ "Félix Saparelli " "Daniel Faust " @@ -6863,10 +6694,6 @@ rec { name = "bitflags"; packageId = "bitflags"; } - { - name = "chashmap"; - packageId = "chashmap"; - } { name = "crossbeam-channel"; packageId = "crossbeam-channel 0.4.2"; @@ -7077,9 +6904,9 @@ rec { }; "num-integer" = rec { crateName = "num-integer"; - version = "0.1.42"; + version = "0.1.43"; edition = "2015"; - sha256 = "1fpw8yr9xwsf3qrh91rm7mzqaiwlc2dmnalsxv9pr9w1klpacviz"; + sha256 = "0nw79ynfvw8br6yncv27pw65y2vw2z7m3kv9g2hinm1dcrz4ancd"; authors = [ "The Rust Project Developers" ]; dependencies = [{ name = "num-traits"; @@ -7099,9 +6926,9 @@ rec { }; "num-iter" = rec { crateName = "num-iter"; - version = "0.1.40"; + version = "0.1.41"; edition = "2015"; - sha256 = "005wif3bk23b5jdg7l0cprzqzyc4jg0xjyzyykciv2ci08581c6z"; + sha256 = "17sb142lhmpsq17cf9wrffjh8vjk901axxf55565r6cgfiy6nvks"; authors = [ "The Rust Project Developers" ]; dependencies = [ { @@ -7165,9 +6992,9 @@ rec { }; "num-traits" = rec { crateName = "num-traits"; - version = "0.2.11"; + version = "0.2.12"; edition = "2015"; - sha256 = "15khrlm1bra50nd48ijl1vln13m9xg4fxzghf28jp16ic5zf8ay6"; + sha256 = "04fnzwlnn6fcy09jjbi9l7bj5dvg657x5c2sjgwfb3pl0z67n9mc"; authors = [ "The Rust Project Developers" ]; buildDependencies = [{ name = "autocfg"; @@ -7393,18 +7220,6 @@ rec { packageId = "shared_library"; }]; - }; - "owning_ref" = rec { - crateName = "owning_ref"; - version = "0.3.3"; - edition = "2015"; - sha256 = "0dqgf5hwbmvkf2ffbik5xmhvaqvqi6iklhwk9x47n0wycd0lzy6d"; - authors = [ "Marvin Löbel " ]; - dependencies = [{ - name = "stable_deref_trait"; - packageId = "stable_deref_trait"; - }]; - }; "packed_simd" = rec { crateName = "packed_simd"; @@ -7524,30 +7339,6 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; - "parking_lot 0.4.8" = rec { - crateName = "parking_lot"; - version = "0.4.8"; - edition = "2015"; - sha256 = "0ph0kv3dfcxpjbi83wkzammqb7lm95j8in7w7hz17hgkjxdqz78l"; - authors = [ "Amanieu d'Antras " ]; - dependencies = [ - { - name = "owning_ref"; - packageId = "owning_ref"; - optional = true; - } - { - name = "parking_lot_core"; - packageId = "parking_lot_core 0.2.14"; - } - ]; - features = { - "deadlock_detection" = [ "parking_lot_core/deadlock_detection" ]; - "default" = [ "owning_ref" ]; - "nightly" = [ "parking_lot_core/nightly" ]; - }; - resolvedDefaultFeatures = [ "default" "owning_ref" ]; - }; "parking_lot 0.9.0" = rec { crateName = "parking_lot"; version = "0.9.0"; @@ -7576,45 +7367,6 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; - "parking_lot_core 0.2.14" = rec { - crateName = "parking_lot_core"; - version = "0.2.14"; - edition = "2015"; - sha256 = "1yip8m6npxb87ilnn0q774psp1zd0vgv66fcjkkvr9rlyz6aicad"; - authors = [ "Amanieu d'Antras " ]; - dependencies = [ - { - name = "libc"; - packageId = "libc"; - target = { target, features }: target."unix"; - } - { - name = "rand"; - packageId = "rand 0.4.6"; - } - { - name = "smallvec"; - packageId = "smallvec 0.6.13"; - } - { - name = "winapi"; - packageId = "winapi 0.3.8"; - target = { target, features }: target."windows"; - features = [ - "winnt" - "ntstatus" - "minwindef" - "winerror" - "winbase" - "errhandlingapi" - "handleapi" - ]; - } - ]; - features = { - "deadlock_detection" = [ "petgraph" "thread-id" "backtrace" ]; - }; - }; "parking_lot_core 0.6.2" = rec { crateName = "parking_lot_core"; version = "0.6.2"; @@ -7792,7 +7544,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "full" "visit-mut" ]; } ]; @@ -7995,28 +7747,6 @@ rec { features = { "default" = [ "std" ]; }; resolvedDefaultFeatures = [ "simd" "std" ]; }; - "pretty_env_logger" = rec { - crateName = "pretty_env_logger"; - version = "0.3.1"; - edition = "2015"; - sha256 = "0x4hyjlnvvhyk9m74iypzybm22w3dl2k8img4b956239n5vf8zki"; - authors = [ "Sean McArthur " ]; - dependencies = [ - { - name = "chrono"; - packageId = "chrono"; - } - { - name = "env_logger"; - packageId = "env_logger"; - } - { - name = "log"; - packageId = "log"; - } - ]; - - }; "proc-macro-hack" = rec { crateName = "proc-macro-hack"; version = "0.5.16"; @@ -8034,22 +7764,6 @@ rec { authors = [ "David Tolnay " ]; }; - "proc-macro2 0.3.8" = rec { - crateName = "proc-macro2"; - version = "0.3.8"; - edition = "2015"; - sha256 = "1ryaynnaj39l4zphcg5w8wszndd80vsrv89m5d2293gl6pry41hv"; - authors = [ "Alex Crichton " ]; - dependencies = [{ - name = "unicode-xid"; - packageId = "unicode-xid 0.1.0"; - }]; - features = { - "default" = [ "proc-macro" ]; - "nightly" = [ "proc-macro" ]; - }; - resolvedDefaultFeatures = [ "default" "proc-macro" ]; - }; "proc-macro2 0.4.30" = rec { crateName = "proc-macro2"; version = "0.4.30"; @@ -8081,9 +7795,9 @@ rec { }; "prometheus" = rec { crateName = "prometheus"; - version = "0.7.0"; + version = "0.9.0"; edition = "2018"; - sha256 = "1wcafa78459f33x31081zi8yfacgalfgz40vbd2wdqkqaxnlhrsm"; + sha256 = "1k8ig1yayxvl96kavsmjdmc19mj9i324viqm1jb6x6p3vrbfs36x"; authors = [ "overvenus@gmail.com" "siddontang@gmail.com" "vistaswx@gmail.com" ]; dependencies = [ @@ -8099,52 +7813,23 @@ rec { name = "lazy_static"; packageId = "lazy_static"; } - { - name = "quick-error"; - packageId = "quick-error"; - } { name = "spin"; packageId = "spin"; } + { + name = "thiserror"; + packageId = "thiserror"; + } ]; features = { "default" = [ "protobuf" ]; "gen" = [ "protobuf-codegen-pure" ]; "nightly" = [ "libc" ]; - "process" = [ "libc" "procinfo" ]; - "push" = [ "reqwest" "libc" ]; + "process" = [ "libc" "procfs" ]; + "push" = [ "reqwest" "libc" "protobuf" ]; }; }; - "prometheus-static-metric" = rec { - crateName = "prometheus-static-metric"; - version = "0.2.0"; - edition = "2015"; - sha256 = "04v06wy1pg1j5dsn8phsyny2wq1zk8lp6kr0hcvzgkr36m0mgahv"; - procMacro = true; - authors = [ "me@breeswish.org" ]; - dependencies = [ - { - name = "lazy_static"; - packageId = "lazy_static"; - } - { - name = "proc-macro2"; - packageId = "proc-macro2 0.3.8"; - } - { - name = "quote"; - packageId = "quote 0.5.2"; - } - { - name = "syn"; - packageId = "syn 0.13.11"; - features = [ "full" "extra-traits" ]; - } - ]; - features = { }; - resolvedDefaultFeatures = [ "default" ]; - }; "qstring" = rec { crateName = "qstring"; version = "0.7.2"; @@ -8168,23 +7853,6 @@ rec { ]; }; - "quote 0.5.2" = rec { - crateName = "quote"; - version = "0.5.2"; - edition = "2015"; - sha256 = "1s01fh0jl8qv4xggs85yahw0h507nzrxkjbf7vay3zw8d3kcyjcr"; - authors = [ "David Tolnay " ]; - dependencies = [{ - name = "proc-macro2"; - packageId = "proc-macro2 0.3.8"; - usesDefaultFeatures = false; - }]; - features = { - "default" = [ "proc-macro" ]; - "proc-macro" = [ "proc-macro2/proc-macro" ]; - }; - resolvedDefaultFeatures = [ "default" "proc-macro" ]; - }; "quote 0.6.13" = rec { crateName = "quote"; version = "0.6.13"; @@ -8230,49 +7898,6 @@ rec { ]; }; - "rand 0.4.6" = rec { - crateName = "rand"; - version = "0.4.6"; - edition = "2015"; - sha256 = "14qjfv3gggzhnma20k0sc1jf8y6pplsaq7n1j9ls5c8kf2wl0a2m"; - authors = [ "The Rust Project Developers" ]; - dependencies = [ - { - name = "fuchsia-cprng"; - packageId = "fuchsia-cprng"; - target = { target, features }: (target."os" == "fuchsia"); - } - { - name = "libc"; - packageId = "libc"; - optional = true; - target = { target, features }: target."unix"; - } - { - name = "rand_core"; - packageId = "rand_core 0.3.1"; - usesDefaultFeatures = false; - target = { target, features }: (target."env" == "sgx"); - } - { - name = "rdrand"; - packageId = "rdrand"; - target = { target, features }: (target."env" == "sgx"); - } - { - name = "winapi"; - packageId = "winapi 0.3.8"; - target = { target, features }: target."windows"; - features = [ "minwindef" "ntsecapi" "profileapi" "winnt" ]; - } - ]; - features = { - "default" = [ "std" ]; - "nightly" = [ "i128_support" ]; - "std" = [ "libc" ]; - }; - resolvedDefaultFeatures = [ "default" "libc" "std" ]; - }; "rand 0.5.6" = rec { crateName = "rand"; version = "0.5.6"; @@ -9053,9 +8678,9 @@ rec { }; "rodio" = rec { crateName = "rodio"; - version = "0.10.0"; + version = "0.11.0"; edition = "2015"; - sha256 = "0qkjnybcxrsrdcdijiprhv624narx8i2m6bfix1cc5wbgkx0s3hy"; + sha256 = "0w3s45pay3q618apgc8yj9z15h951mz85mhpg8dm1m9g4rhg5fvk"; authors = [ "Pierre Krieger " ]; dependencies = [ { @@ -9533,9 +9158,9 @@ rec { }; "serde" = rec { crateName = "serde"; - version = "1.0.111"; + version = "1.0.114"; edition = "2015"; - sha256 = "0zg3i95c663yqyfmqpwr1l3s6h60jjw6mk5jh003ig8cnksls4n9"; + sha256 = "1lwcxlh8c09cs6qmwr6w68hl989mczwmwrzgc3p7hl0aixcgf5sk"; authors = [ "Erick Tryzelaar " "David Tolnay " @@ -9557,9 +9182,9 @@ rec { }; "serde_derive" = rec { crateName = "serde_derive"; - version = "1.0.111"; + version = "1.0.114"; edition = "2015"; - sha256 = "0l5j3v7g4lj18w4iifzz7jp83gv206a2645yp209q7nawv43lb1z"; + sha256 = "13lgjxsc617yhblm779jwg43gxab2dfgrpyd6znvl3v90i5yj2ra"; procMacro = true; authors = [ "Erick Tryzelaar " @@ -9576,7 +9201,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "visit" ]; } ]; @@ -9691,14 +9316,14 @@ rec { }; "shred" = rec { crateName = "shred"; - version = "0.9.4"; + version = "0.10.2"; edition = "2018"; - sha256 = "1k3czyp7xaa99984k33pyr12fcz54kcl8r2wjchvmg5gmydjniwj"; + sha256 = "0v81g5vwa8bwqm388hiinb6966nr8dcqp1zq42nr9b37wqvq5w65"; authors = [ "torkleyy " ]; dependencies = [ { name = "arrayvec"; - packageId = "arrayvec 0.4.12"; + packageId = "arrayvec 0.5.1"; } { name = "hashbrown"; @@ -9720,7 +9345,11 @@ rec { } { name = "smallvec"; - packageId = "smallvec 0.6.13"; + packageId = "smallvec 1.4.0"; + } + { + name = "tynm"; + packageId = "tynm"; } ]; devDependencies = [{ @@ -9731,8 +9360,7 @@ rec { "default" = [ "parallel" "shred-derive" ]; "parallel" = [ "rayon" ]; }; - resolvedDefaultFeatures = - [ "nightly" "parallel" "rayon" "shred-derive" ]; + resolvedDefaultFeatures = [ "parallel" "rayon" "shred-derive" ]; }; "shred-derive" = rec { crateName = "shred-derive"; @@ -9752,7 +9380,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; } ]; @@ -9925,18 +9553,19 @@ rec { }; "specs" = rec { crateName = "specs"; - version = "0.15.1"; + version = "0.16.1"; edition = "2018"; - sha256 = "1339xf7s95124lswvrhr18knqqkvpnmwg96j34ylrlfkqplgshs9"; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/amethyst/specs.git"; + rev = "7a2e348ab2223818bad487695c66c43db88050a5"; + sha256 = "1z7gjiq7zirg9az3ly1y2ghi5m7s3x1bp35gw5x0cyv50fsi3pjq"; + }; authors = [ "slide-rs hackers" ]; dependencies = [ { name = "crossbeam-queue"; - packageId = "crossbeam-queue 0.1.2"; - } - { - name = "derivative"; - packageId = "derivative"; + packageId = "crossbeam-queue 0.2.3"; } { name = "hashbrown"; @@ -9979,11 +9608,12 @@ rec { devDependencies = [{ name = "shred"; packageId = "shred"; + usesDefaultFeatures = false; features = [ "shred-derive" ]; }]; features = { "default" = [ "parallel" ]; - "nightly" = [ "shred/nightly" ]; + "derive" = [ "shred-derive" "specs-derive" ]; "parallel" = [ "rayon" "shred/parallel" "hibitset/parallel" ]; "shred-derive" = [ "shred/shred-derive" ]; "stdweb" = [ "uuid/stdweb" ]; @@ -9992,7 +9622,6 @@ rec { }; resolvedDefaultFeatures = [ "default" - "nightly" "parallel" "rayon" "serde" @@ -10007,8 +9636,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://gitlab.com/veloren/specs-idvs.git"; - rev = "111548cf44e9bb4c43feb12aa3fc253953ac6e4a"; - sha256 = "1qgbzr4g4iys10hi56l6z7k3gk9qwj9xamfdigqnkhxvcynfhqqv"; + rev = "fcb0b2306b571f62f9f85d89e79e087454d95efd"; + sha256 = "00w4kc60d7mjb5gbkcxrxzarshmhf4idqm3sk8335f7s3pn9q0s5"; }; authors = [ "Acrimon " ]; dependencies = [{ @@ -10028,15 +9657,6 @@ rec { ]; }; - "stable_deref_trait" = rec { - crateName = "stable_deref_trait"; - version = "1.1.1"; - edition = "2015"; - sha256 = "1j2lkgakksmz4vc5hfawcch2ipiskrhjs1sih0f3br7s7rys58fv"; - authors = [ "Robert Grosse " ]; - features = { "default" = [ "std" ]; }; - resolvedDefaultFeatures = [ "default" "std" ]; - }; "static_assertions" = rec { crateName = "static_assertions"; version = "1.1.0"; @@ -10162,7 +9782,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; usesDefaultFeatures = false; features = [ "derive" "parsing" "printing" ]; } @@ -10207,7 +9827,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; usesDefaultFeatures = false; features = [ "full" "parsing" "printing" "clone-impls" ]; } @@ -10262,47 +9882,6 @@ rec { authors = [ "Nicolas Silva " ]; }; - "syn 0.13.11" = rec { - crateName = "syn"; - version = "0.13.11"; - edition = "2015"; - sha256 = "16qvx8qyb5v4vjbg9rk8848bw6x4i6vzs8v7f4n1v9pkj9ibzy8l"; - authors = [ "David Tolnay " ]; - dependencies = [ - { - name = "proc-macro2"; - packageId = "proc-macro2 0.3.8"; - usesDefaultFeatures = false; - } - { - name = "quote"; - packageId = "quote 0.5.2"; - optional = true; - usesDefaultFeatures = false; - } - { - name = "unicode-xid"; - packageId = "unicode-xid 0.1.0"; - } - ]; - features = { - "default" = - [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ]; - "printing" = [ "quote" ]; - "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ]; - }; - resolvedDefaultFeatures = [ - "clone-impls" - "default" - "derive" - "extra-traits" - "full" - "parsing" - "printing" - "proc-macro" - "quote" - ]; - }; "syn 0.15.44" = rec { crateName = "syn"; version = "0.15.44"; @@ -10345,11 +9924,11 @@ rec { "visit" ]; }; - "syn 1.0.31" = rec { + "syn 1.0.33" = rec { crateName = "syn"; - version = "1.0.31"; + version = "1.0.33"; edition = "2018"; - sha256 = "1dm8vd1kn2f7i7nprwc8xwhys5jhnf8szm15bicbfrbkybylqc5m"; + sha256 = "1kdj0piws00cc0rgn2315625dfxfpxrzf6gib5lms05viipdkmg8"; authors = [ "David Tolnay " ]; dependencies = [ { @@ -10409,7 +9988,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; usesDefaultFeatures = false; features = [ "derive" @@ -10432,19 +10011,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "proc-macro" ]; }; - "termcolor" = rec { - crateName = "termcolor"; - version = "1.1.0"; - edition = "2018"; - sha256 = "0pyp8vc0gx7124y80ixdl6plbfn1yjhw04i875k5fz2dk8lglsxv"; - authors = [ "Andrew Gallant " ]; - dependencies = [{ - name = "winapi-util"; - packageId = "winapi-util"; - target = { target, features }: target."windows"; - }]; - - }; "textwrap" = rec { crateName = "textwrap"; version = "0.11.0"; @@ -10456,6 +10022,41 @@ rec { packageId = "unicode-width"; }]; + }; + "thiserror" = rec { + crateName = "thiserror"; + version = "1.0.20"; + edition = "2018"; + sha256 = "020d7pfq7vg2iw1bbilnyq764zy35ncg2syn9a7vgk6qriqd1zbx"; + authors = [ "David Tolnay " ]; + dependencies = [{ + name = "thiserror-impl"; + packageId = "thiserror-impl"; + }]; + + }; + "thiserror-impl" = rec { + crateName = "thiserror-impl"; + version = "1.0.20"; + edition = "2018"; + sha256 = "14qphjwa68sfjk3404iwv6hh8kvk6vmcwan9589sqqrhyw9gr05x"; + procMacro = true; + authors = [ "David Tolnay " ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2 1.0.18"; + } + { + name = "quote"; + packageId = "quote 1.0.7"; + } + { + name = "syn"; + packageId = "syn 1.0.33"; + } + ]; + }; "thread_local" = rec { crateName = "thread_local"; @@ -10642,6 +10243,15 @@ rec { ]; }; + "tinyvec" = rec { + crateName = "tinyvec"; + version = "0.3.3"; + edition = "2018"; + sha256 = "1vgg2z317kq75bpd0nfda2v507qjpd7g2cjahjgivn2s78nkv5ak"; + authors = [ "Lokathor " ]; + features = { }; + resolvedDefaultFeatures = [ "alloc" "default" ]; + }; "tokio" = rec { crateName = "tokio"; version = "0.1.22"; @@ -11045,6 +10655,11 @@ rec { name = "cfg-if"; packageId = "cfg-if"; } + { + name = "tracing-attributes"; + packageId = "tracing-attributes"; + optional = true; + } { name = "tracing-core"; packageId = "tracing-core"; @@ -11057,7 +10672,61 @@ rec { "log-always" = [ "log" ]; "std" = [ "tracing-core/std" ]; }; - resolvedDefaultFeatures = [ "std" ]; + resolvedDefaultFeatures = + [ "attributes" "default" "std" "tracing-attributes" ]; + }; + "tracing-appender" = rec { + crateName = "tracing-appender"; + version = "0.1.0"; + edition = "2018"; + sha256 = "1h19cskjxvzpv685ybb0vsfn6r585mzz4b7hzm7z8m4vyqgsd1hy"; + authors = [ + "Zeki Sherif " + "Tokio Contributors " + ]; + dependencies = [ + { + name = "chrono"; + packageId = "chrono"; + } + { + name = "crossbeam-channel"; + packageId = "crossbeam-channel 0.4.2"; + } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + } + ]; + + }; + "tracing-attributes" = rec { + crateName = "tracing-attributes"; + version = "0.1.8"; + edition = "2018"; + sha256 = "0k4qvq437md3zynm8qbas6jfb0xp222xisij6af3r4pxwc6svfwr"; + procMacro = true; + authors = [ + "Tokio Contributors " + "Eliza Weisman " + "David Barsky " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2 1.0.18"; + } + { + name = "quote"; + packageId = "quote 1.0.7"; + } + { + name = "syn"; + packageId = "syn 1.0.33"; + features = [ "full" "extra-traits" ]; + } + ]; + features = { }; }; "tracing-core" = rec { crateName = "tracing-core"; @@ -11107,11 +10776,56 @@ rec { resolvedDefaultFeatures = [ "default" "pin-project" "std" "std-future" ]; }; + "tracing-log" = rec { + crateName = "tracing-log"; + version = "0.1.1"; + edition = "2018"; + sha256 = "1fdr0az98q9m5kiybvdvsb2m9mg86fdidgb5czzq2d71g1qqq3sy"; + authors = [ "Tokio Contributors " ]; + dependencies = [ + { + name = "lazy_static"; + packageId = "lazy_static"; + } + { + name = "log"; + packageId = "log"; + } + { + name = "tracing-core"; + packageId = "tracing-core"; + } + ]; + features = { + "default" = [ "log-tracer" "trace-logger" "std" ]; + "std" = [ "log/std" ]; + }; + resolvedDefaultFeatures = + [ "default" "log-tracer" "std" "trace-logger" ]; + }; + "tracing-serde" = rec { + crateName = "tracing-serde"; + version = "0.1.1"; + edition = "2018"; + sha256 = "0ybfv0bzx542xkqzhcjx33knbs92zyvxjrf7iwkfvq0niwpvmk5n"; + authors = [ "Tokio Contributors " ]; + dependencies = [ + { + name = "serde"; + packageId = "serde"; + } + { + name = "tracing-core"; + packageId = "tracing-core"; + } + ]; + + }; "tracing-subscriber" = rec { crateName = "tracing-subscriber"; - version = "0.2.5"; + version = "0.2.6"; edition = "2018"; - sha256 = "1li6g1hw2q3wz39c5g9kvw1q9jl6d3r87x0zsapcjsdai42c8lqx"; + sha256 = "06d1lb92zknb4isd1xc993a12ahlykzbm05mw7v8zqq9j52ip884"; authors = [ "Eliza Weisman " "David Barsky " @@ -11143,6 +10857,16 @@ rec { packageId = "regex"; optional = true; } + { + name = "serde"; + packageId = "serde"; + optional = true; + } + { + name = "serde_json"; + packageId = "serde_json"; + optional = true; + } { name = "sharded-slab"; packageId = "sharded-slab"; @@ -11157,7 +10881,23 @@ rec { name = "tracing-core"; packageId = "tracing-core"; } + { + name = "tracing-log"; + packageId = "tracing-log"; + optional = true; + usesDefaultFeatures = false; + features = [ "log-tracer" "std" ]; + } + { + name = "tracing-serde"; + packageId = "tracing-serde"; + optional = true; + } ]; + devDependencies = [{ + name = "tracing-log"; + packageId = "tracing-log"; + }]; features = { "ansi" = [ "fmt" "ansi_term" ]; "default" = [ @@ -11178,14 +10918,20 @@ rec { "ansi" "ansi_term" "chrono" + "default" "env-filter" "fmt" + "json" "lazy_static" "matchers" "regex" "registry" + "serde" + "serde_json" "sharded-slab" "smallvec" + "tracing-log" + "tracing-serde" ]; }; "treeculler" = rec { @@ -11226,6 +10972,20 @@ rec { sha256 = "1wgl7a32a9gvcxiyxznmglc9kc4d2j7c4dfzpr3nzcf5w8c490s4"; authors = [ "Colin Sherratt " ]; + }; + "tynm" = rec { + crateName = "tynm"; + version = "0.1.4"; + edition = "2018"; + sha256 = "0knk747j6hl2fw2i008z135sxjki315waqik1alv8q9rjs0vfzrn"; + authors = [ "Azriel Hoh " ]; + dependencies = [{ + name = "nom"; + packageId = "nom 5.1.1"; + usesDefaultFeatures = false; + features = [ "std" ]; + }]; + }; "unicode-bidi" = rec { crateName = "unicode-bidi"; @@ -11594,12 +11354,15 @@ rec { authors = [ "Joshua Barretto " ]; dependencies = [ { - name = "log"; - packageId = "log"; + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; } { - name = "pretty_env_logger"; - packageId = "pretty_env_logger"; + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + usesDefaultFeatures = false; + features = [ "fmt" "chrono" "ansi" "smallvec" ]; } { name = "veloren-client"; @@ -11632,6 +11395,18 @@ rec { name = "byteorder"; packageId = "byteorder 1.3.4"; } + { + name = "futures-executor"; + packageId = "futures-executor"; + } + { + name = "futures-timer"; + packageId = "futures-timer"; + } + { + name = "futures-util"; + packageId = "futures-util"; + } { name = "hashbrown"; packageId = "hashbrown"; @@ -11643,10 +11418,6 @@ rec { usesDefaultFeatures = false; features = [ "png" ]; } - { - name = "log"; - packageId = "log"; - } { name = "num_cpus"; packageId = "num_cpus"; @@ -11655,6 +11426,11 @@ rec { name = "specs"; packageId = "specs"; } + { + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; + } { name = "uvth"; packageId = "uvth 3.1.1"; @@ -11670,6 +11446,12 @@ rec { rename = "common"; features = [ "no-assets" ]; } + { + name = "veloren_network"; + packageId = "veloren_network"; + rename = "network"; + usesDefaultFeatures = false; + } ]; }; @@ -11691,10 +11473,6 @@ rec { name = "authc"; packageId = "authc"; } - { - name = "bincode"; - packageId = "bincode"; - } { name = "crossbeam"; packageId = "crossbeam"; @@ -11726,18 +11504,6 @@ rec { name = "lazy_static"; packageId = "lazy_static"; } - { - name = "log"; - packageId = "log"; - } - { - name = "lz4-compress"; - packageId = "lz4-compress"; - } - { - name = "mio"; - packageId = "mio"; - } { name = "notify"; packageId = "notify"; @@ -11762,10 +11528,7 @@ rec { { name = "serde"; packageId = "serde"; - } - { - name = "serde_derive"; - packageId = "serde_derive"; + features = [ "derive" ]; } { name = "serde_json"; @@ -11774,7 +11537,7 @@ rec { { name = "specs"; packageId = "specs"; - features = [ "serde" "nightly" "storage-event-control" ]; + features = [ "serde" "storage-event-control" ]; } { name = "specs-idvs"; @@ -11784,6 +11547,11 @@ rec { name = "sum_type"; packageId = "sum_type"; } + { + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; + } { name = "vek"; packageId = "vek"; @@ -11832,6 +11600,18 @@ rec { name = "dotenv"; packageId = "dotenv"; } + { + name = "futures-executor"; + packageId = "futures-executor"; + } + { + name = "futures-timer"; + packageId = "futures-timer"; + } + { + name = "futures-util"; + packageId = "futures-util"; + } { name = "hashbrown"; packageId = "hashbrown"; @@ -11846,10 +11626,6 @@ rec { packageId = "libsqlite3-sys"; features = [ "bundled" ]; } - { - name = "log"; - packageId = "log"; - } { name = "portpicker"; packageId = "portpicker"; @@ -11859,10 +11635,6 @@ rec { packageId = "prometheus"; usesDefaultFeatures = false; } - { - name = "prometheus-static-metric"; - packageId = "prometheus-static-metric"; - } { name = "rand"; packageId = "rand 0.7.3"; @@ -11880,10 +11652,7 @@ rec { { name = "serde"; packageId = "serde"; - } - { - name = "serde_derive"; - packageId = "serde_derive"; + features = [ "derive" ]; } { name = "serde_json"; @@ -11902,6 +11671,10 @@ rec { name = "tiny_http"; packageId = "tiny_http"; } + { + name = "tracing"; + packageId = "tracing"; + } { name = "uvth"; packageId = "uvth 3.1.1"; @@ -11920,6 +11693,12 @@ rec { packageId = "veloren-world"; rename = "world"; } + { + name = "veloren_network"; + packageId = "veloren_network"; + rename = "network"; + usesDefaultFeatures = false; + } ]; features = { "default" = [ "worldgen" ]; }; resolvedDefaultFeatures = [ "default" "worldgen" ]; @@ -11939,12 +11718,15 @@ rec { authors = [ "Joshua Barretto " ]; dependencies = [ { - name = "log"; - packageId = "log"; + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; } { - name = "pretty_env_logger"; - packageId = "pretty_env_logger"; + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + usesDefaultFeatures = false; + features = [ "env-filter" "fmt" "chrono" "ansi" "smallvec" ]; } { name = "veloren-common"; @@ -12023,8 +11805,8 @@ rec { packageId = "deunicode"; } { - name = "directories"; - packageId = "directories"; + name = "directories-next"; + packageId = "directories-next"; } { name = "dispatch"; @@ -12043,11 +11825,6 @@ rec { name = "failure"; packageId = "failure"; } - { - name = "fern"; - packageId = "fern"; - features = [ "colored" ]; - } { name = "gfx"; packageId = "gfx"; @@ -12089,10 +11866,6 @@ rec { usesDefaultFeatures = false; features = [ "ico" "png" ]; } - { - name = "log"; - packageId = "log"; - } { name = "msgbox"; packageId = "msgbox"; @@ -12134,6 +11907,25 @@ rec { name = "specs-idvs"; packageId = "specs-idvs"; } + { + name = "tracing"; + packageId = "tracing"; + } + { + name = "tracing-appender"; + packageId = "tracing-appender"; + } + { + name = "tracing-log"; + packageId = "tracing-log"; + } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + usesDefaultFeatures = false; + features = + [ "env-filter" "fmt" "chrono" "ansi" "smallvec" "tracing-log" ]; + } { name = "treeculler"; packageId = "treeculler"; @@ -12232,6 +12024,11 @@ rec { "Imbris " ]; dependencies = [ + { + name = "find_folder"; + packageId = "find_folder"; + optional = true; + } { name = "lazy_static"; packageId = "lazy_static"; @@ -12243,13 +12040,13 @@ rec { optional = true; } { - name = "log"; - packageId = "log"; + name = "notify"; + packageId = "notify"; optional = true; } { - name = "notify"; - packageId = "notify"; + name = "tracing"; + packageId = "tracing"; optional = true; } { @@ -12265,15 +12062,17 @@ rec { ]; features = { "default" = [ "be-dyn-lib" ]; - "use-dyn-lib" = [ "libloading" "notify" "lazy_static" "log" ]; + "use-dyn-lib" = + [ "libloading" "notify" "lazy_static" "tracing" "find_folder" ]; }; resolvedDefaultFeatures = [ "be-dyn-lib" "default" + "find_folder" "lazy_static" "libloading" - "log" "notify" + "tracing" "use-dyn-lib" ]; }; @@ -12316,16 +12115,12 @@ rec { } { name = "itertools"; - packageId = "itertools 0.8.2"; + packageId = "itertools"; } { name = "lazy_static"; packageId = "lazy_static"; } - { - name = "log"; - packageId = "log"; - } { name = "noise"; packageId = "noise"; @@ -12367,10 +12162,12 @@ rec { { name = "serde"; packageId = "serde"; + features = [ "derive" ]; } { - name = "serde_derive"; - packageId = "serde_derive"; + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; } { name = "vek"; @@ -12388,8 +12185,10 @@ rec { packageId = "minifb"; } { - name = "pretty_env_logger"; - packageId = "pretty_env_logger"; + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + usesDefaultFeatures = false; + features = [ "fmt" "chrono" "ansi" "smallvec" ]; } ]; @@ -12414,6 +12213,10 @@ rec { name = "bincode"; packageId = "bincode"; } + { + name = "crossbeam-channel"; + packageId = "crossbeam-channel 0.4.2"; + } { name = "futures"; packageId = "futures 0.3.5"; @@ -12424,6 +12227,10 @@ rec { packageId = "lazy_static"; usesDefaultFeatures = false; } + { + name = "lz4-compress"; + packageId = "lz4-compress"; + } { name = "prometheus"; packageId = "prometheus"; @@ -12603,7 +12410,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "full" ]; } { @@ -12654,7 +12461,7 @@ rec { } { name = "syn"; - packageId = "syn 1.0.31"; + packageId = "syn 1.0.33"; features = [ "visit" ]; } { diff --git a/nix/crate-hashes.json b/nix/crate-hashes.json index 4c18b873ce..423321de95 100644 --- a/nix/crate-hashes.json +++ b/nix/crate-hashes.json @@ -8,6 +8,7 @@ "guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere#42c298f5bcf0f95f1a004360d05e25ca3711e9ed)": "1knqbn777f3cgzbsaqawzclgrqf3nav6x3gjzc2hsls3acccx1ya", "msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a#68fe39a60019b38a1569ae4e9ed796a0f0542673)": "18h6s50n7mz2ggfishhi91p2298shqhzx5xagpg9q4zb4y90c2wr", "portpicker 0.1.0 (git+https://github.com/xMAC94x/portpicker-rs#9d6df36c53c94684080a64a7212dd6bfc3617ee4)": "00vl2k3xfihxq86kf5rsknjl8dxmrxqhwajwn0hj4gzgnbssr0rx", - "specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git#111548cf44e9bb4c43feb12aa3fc253953ac6e4a)": "1qgbzr4g4iys10hi56l6z7k3gk9qwj9xamfdigqnkhxvcynfhqqv", + "specs 0.16.1 (git+https://github.com/amethyst/specs.git?rev=7a2e348ab2223818bad487695c66c43db88050a5#7a2e348ab2223818bad487695c66c43db88050a5)": "1z7gjiq7zirg9az3ly1y2ghi5m7s3x1bp35gw5x0cyv50fsi3pjq", + "specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git?branch=specs-git#fcb0b2306b571f62f9f85d89e79e087454d95efd)": "00w4kc60d7mjb5gbkcxrxzarshmhf4idqm3sk8335f7s3pn9q0s5", "treeculler 0.1.0 (git+https://gitlab.com/yusdacra/treeculler.git#efcf5283cf386117a7e654abdaa45ef664a08e42)": "19niwgha0jnvrp22pq0070dfimb2wkda53a3parhga3vhap2g01b" } \ No newline at end of file diff --git a/nix/default.nix b/nix/default.nix index 7814f52e37..0d071cac35 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,12 +1,23 @@ { crateName ? "veloren-voxygen", # `crate2nix` doesn't support profiles in `Cargo.toml`, so default to release. Otherwise bad performance (non-release is built with opt level 0) -release ? true, sources ? import ./sources.nix { }, nixpkgsSrc ? }: +release ? true, nixpkgs ? , system ? builtins.currentSystem +, sources ? import ./sources.nix { inherit system; } }: let - # Check if git-lfs is working. - isGitLfsSetup = + gitHash = + # Check if git-lfs is working. if builtins.pathExists ../assets/voxygen/background/bg_main.png then - true + builtins.readFile (pkgs.runCommand "getGitHash" { } '' + cd ${ + # Only copy the `.git` directory to nix store, anything else is a waste. + builtins.path { + path = ../.git; + # Nix store path names don't accept names that start with a dot. + name = "git"; + } + } + ${pkgs.git}/bin/git log -n 1 --pretty=format:%h/%cd --date=format:%Y-%m-%d-%H:%M --abbrev=8 > $out + '') else abort '' Git Large File Storage (git-lfs) has not been set up correctly. @@ -17,34 +28,17 @@ let See the book at https://book.veloren.net/ for details. ''; - pkgs = import ./nixpkgs.nix { inherit sources nixpkgsSrc; }; - - # Only copy the `.git` directory to nix store, anything else is a waste. - gitSrc = builtins.path { - path = ../.git; - name = "git"; - }; - gitHash = builtins.readFile (with pkgs; - runCommand "getGitHash" { nativeBuildInputs = [ git ]; } '' - cd ${gitSrc} - git log -n 1 --pretty=format:%h/%cd --date=format:%Y-%m-%d-%H:%M --abbrev=8 > $out - ''); + pkgs = import ./nixpkgs.nix { inherit sources nixpkgs system; }; veloren = with pkgs; callPackage ./Cargo.nix { defaultCrateOverrides = defaultCrateOverrides // { - libudev-sys = attrs: { buildInputs = [ pkg-config libudev ]; }; - alsa-sys = attrs: { buildInputs = [ pkg-config alsaLib ]; }; - veloren-common = attrs: { - NIX_GIT_HASH = gitHash; - # We need to include the result here otherwise nix won't evaluate the check. - GIT_LFS_SETUP = isGitLfsSetup; - }; - veloren-network = attrs: { buildInputs = [ pkg-config openssl ]; }; - veloren-voxygen = attrs: { - buildInputs = [ atk cairo glib gtk3 pango ]; - }; + libudev-sys = _: { buildInputs = [ pkg-config libudev ]; }; + alsa-sys = _: { buildInputs = [ pkg-config alsaLib ]; }; + veloren-common = _: { NIX_GIT_HASH = gitHash; }; + veloren-network = _: { buildInputs = [ pkg-config openssl ]; }; + veloren-voxygen = _: { buildInputs = [ atk cairo glib gtk3 pango ]; }; }; - inherit release pkgs; + inherit release pkgs nixpkgs; }; in veloren.workspaceMembers."${crateName}".build diff --git a/nix/nixpkgs.nix b/nix/nixpkgs.nix index de8cac46ee..a4aae61026 100644 --- a/nix/nixpkgs.nix +++ b/nix/nixpkgs.nix @@ -1,11 +1,16 @@ -{ sources ? import ./sources.nix { }, nixpkgsSrc ? }: +{ nixpkgs ? , system ? builtins.currentSystem +, sources ? import ./sources.nix { inherit system; } }: let mozPkgs = import "${sources.nixpkgsMoz}/package-set.nix" { - pkgs = import nixpkgsSrc { }; + pkgs = import nixpkgs { inherit system; }; }; - rustChannel = mozPkgs.rustChannelOf { rustToolchain = ../rust-toolchain; }; -in import nixpkgsSrc { + rustChannel = mozPkgs.rustChannelOf { + rustToolchain = ../rust-toolchain; + sha256 = "sha256-18R7sZfLGmtYkz24jUaq268fJO2A71p+dWvGm4DgqEw="; + }; +in import nixpkgs { + inherit system; overlays = [ (self: super: { rustc = rustChannel.rust; diff --git a/nix/shell.nix b/nix/shell.nix index b2f2b67959..ec3b89aea3 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -1,6 +1,9 @@ -{ sources ? import ./sources.nix { }, pkgs ? import { } }: +{ nixpkgs ? , sources ? import ./sources.nix { } +, system ? builtins.currentSystem }: -let crate2nix = import sources.crate2nix { }; +let + pkgs = import ./nixpkgs.nix { inherit sources nixpkgs system; }; + crate2nix = import sources.crate2nix { inherit pkgs; }; in pkgs.mkShell { name = "veloren-shell"; nativeBuildInputs = with pkgs; [ @@ -11,7 +14,8 @@ in pkgs.mkShell { niv nixfmt crate2nix - rustup + cargo + rustc ]; buildInputs = with pkgs; [ alsaLib diff --git a/nix/sources.json b/nix/sources.json index 78a8c2782b..3f3f618c5f 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": "", "owner": "kolloch", "repo": "crate2nix", - "rev": "87a544222723693c81f3b4141d639ba9b5769e2e", - "sha256": "0rml8xy7b6qlx631wpa29yb8qjsilv55w6g5rqbqjyw22aqz4ppb", + "rev": "5dfb0c155ca639591b82106b6455f24fc0a2f484", + "sha256": "0nkvpk3m8wgnwd94520jnsxjq14q255s67rl885i75rm293j2yha", "type": "tarball", - "url": "https://github.com/kolloch/crate2nix/archive/87a544222723693c81f3b4141d639ba9b5769e2e.tar.gz", + "url": "https://github.com/kolloch/crate2nix/archive/5dfb0c155ca639591b82106b6455f24fc0a2f484.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "nixpkgsMoz": { @@ -17,10 +17,10 @@ "homepage": null, "owner": "mozilla", "repo": "nixpkgs-mozilla", - "rev": "e912ed483e980dfb4666ae0ed17845c4220e5e7c", - "sha256": "08fvzb8w80bkkabc1iyhzd15f4sm7ra10jn32kfch5klgl0gj3j3", + "rev": "18cd4300e9bf61c7b8b372f07af827f6ddc835bb", + "sha256": "1s0d1l5y7a3kygjbibssjnj7fcc87qaa5s9k4kda0j13j9h4zwgr", "type": "tarball", - "url": "https://github.com/mozilla/nixpkgs-mozilla/archive/e912ed483e980dfb4666ae0ed17845c4220e5e7c.tar.gz", + "url": "https://github.com/mozilla/nixpkgs-mozilla/archive/18cd4300e9bf61c7b8b372f07af827f6ddc835bb.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/sources.nix b/nix/sources.nix index 0286dec14a..b20abf2307 100644 --- a/nix/sources.nix +++ b/nix/sources.nix @@ -1,5 +1,5 @@ # This file has been generated by Niv. - +{ system ? builtins.currentSystem }: let # @@ -61,7 +61,7 @@ let in if builtins.hasAttr "nixpkgs" sources then sourcesNixpkgs else if hasNixpkgsPath && !hasThisAsNixpkgsPath then - import { } + import { inherit system; } else abort '' Please specify either (through -I or NIX_PATH=nixpkgs=...) or diff --git a/server/Cargo.toml b/server/Cargo.toml index bf230aab9e..c26a55f683 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,7 +11,7 @@ default = ["worldgen"] [dependencies] common = { package = "veloren-common", path = "../common" } world = { package = "veloren-world", path = "../world" } -network = { package = "veloren_network", path = "../network", default-features = false } +network = { package = "veloren_network", path = "../network", features = ["metrics"], default-features = false } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" } diff --git a/server/src/alias_validator.rs b/server/src/alias_validator.rs new file mode 100644 index 0000000000..d6e02f09c9 --- /dev/null +++ b/server/src/alias_validator.rs @@ -0,0 +1,115 @@ +use std::fmt::{self, Display}; + +#[derive(Debug, Default)] +pub struct AliasValidator { + banned_substrings: Vec, +} + +impl AliasValidator { + pub fn new(banned_substrings: Vec) -> Self { + let banned_substrings = banned_substrings + .iter() + .map(|string| string.to_lowercase()) + .collect(); + + AliasValidator { banned_substrings } + } + + pub fn validate(&self, alias: &str) -> Result<(), ValidatorError> { + let lowercase_alias = alias.to_lowercase(); + + for banned_word in self.banned_substrings.iter() { + if lowercase_alias.contains(banned_word) { + return Err(ValidatorError::Forbidden( + alias.to_owned(), + banned_word.to_owned(), + )); + } + } + Ok(()) + } +} + +#[derive(Debug, PartialEq)] +pub enum ValidatorError { + Forbidden(String, String), +} + +impl Display for ValidatorError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Forbidden(name, _) => write!( + formatter, + "Character name \"{}\" contains a banned word", + name + ), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn multiple_matches() { + let banned_substrings = vec!["bad".to_owned(), "worse".to_owned()]; + let validator = AliasValidator::new(banned_substrings); + + let bad_alias = "Badplayery Mc WorsePlayeryFace"; + let result = validator.validate(bad_alias); + + assert_eq!( + result, + Err(ValidatorError::Forbidden( + bad_alias.to_owned(), + "bad".to_owned() + )) + ); + } + + #[test] + fn single_lowercase_match() { + let banned_substrings = vec!["blue".to_owned()]; + let validator = AliasValidator::new(banned_substrings); + + let bad_alias = "blueName"; + let result = validator.validate(bad_alias); + + assert_eq!( + result, + Err(ValidatorError::Forbidden( + bad_alias.to_owned(), + "blue".to_owned() + )) + ); + } + + #[test] + fn single_case_insensitive_match() { + let banned_substrings = vec!["GrEEn".to_owned()]; + let validator = AliasValidator::new(banned_substrings); + + let bad_alias = "gReenName"; + let result = validator.validate(bad_alias); + + assert_eq!( + result, + Err(ValidatorError::Forbidden( + bad_alias.to_owned(), + "green".to_owned() + )) + ); + } + + #[test] + fn mp_matches() { + let banned_substrings = vec!["orange".to_owned()]; + let validator = AliasValidator::new(banned_substrings); + + let good_alias = "ReasonableName"; + let result = validator.validate(good_alias); + + assert_eq!(result, Ok(())); + } +} diff --git a/server/src/client.rs b/server/src/client.rs index 412d36a7d1..95e6d96b91 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,13 +1,21 @@ -use common::msg::{ClientState, RequestStateError, ServerMsg}; +use crate::error::Error; +use common::msg::{ClientMsg, ClientState, RequestStateError, ServerMsg}; use hashbrown::HashSet; -use network::Stream; +use network::{Participant, Stream}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, +}; +use tracing::debug; use vek::*; pub struct Client { pub client_state: ClientState, + pub participant: Mutex>, pub singleton_stream: Stream, + pub network_error: AtomicBool, pub last_ping: f64, pub login_msg_sent: bool, } @@ -17,7 +25,29 @@ impl Component for Client { } impl Client { - pub fn notify(&mut self, msg: ServerMsg) { let _ = self.singleton_stream.send(msg); } + pub fn notify(&mut self, msg: ServerMsg) { + if !self.network_error.load(Ordering::Relaxed) { + if let Err(e) = self.singleton_stream.send(msg) { + debug!(?e, "got a network error with client"); + self.network_error.store(true, Ordering::Relaxed); + } + } + } + + pub async fn recv(&mut self) -> Result { + if !self.network_error.load(Ordering::Relaxed) { + match self.singleton_stream.recv().await { + Ok(r) => Ok(r), + Err(e) => { + debug!(?e, "got a network error with client while recv"); + self.network_error.store(true, Ordering::Relaxed); + Err(Error::StreamErr(e)) + }, + } + } else { + Err(Error::StreamErr(network::StreamError::StreamClosed)) + } + } pub fn is_registered(&self) -> bool { match self.client_state { @@ -41,9 +71,7 @@ impl Client { } pub fn error_state(&mut self, error: RequestStateError) { - let _ = self - .singleton_stream - .send(ServerMsg::StateAnswer(Err((error, self.client_state)))); + let _ = self.notify(ServerMsg::StateAnswer(Err((error, self.client_state)))); } } diff --git a/server/src/error.rs b/server/src/error.rs index a231b3ec34..74fadd4f74 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -1,5 +1,7 @@ use network::{NetworkError, ParticipantError, StreamError}; +use std::fmt::{self, Display}; + #[derive(Debug)] pub enum Error { NetworkErr(NetworkError), @@ -19,3 +21,14 @@ impl From for Error { impl From for Error { fn from(err: StreamError) -> Self { Error::StreamErr(err) } } + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NetworkErr(err) => write!(f, "Network Error: {}", err), + Self::ParticipantErr(err) => write!(f, "Participant Error: {}", err), + Self::StreamErr(err) => write!(f, "Stream Error: {}", err), + Self::Other(err) => write!(f, "Error: {}", err), + } + } +} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 454f8f95e9..521acfbed1 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,11 +1,14 @@ use crate::{client::Client, Server, SpawnPoint, StateExt}; use common::{ assets, - comp::{self, item::lottery::Lottery, object, Body, HealthChange, HealthSource, Player, Stats}, + comp::{ + self, item::lottery::Lottery, object, Body, Damage, DamageSource, HealthChange, + HealthSource, Player, Stats, + }, msg::{PlayerListUpdate, ServerMsg}, state::BlockChange, sync::{Uid, WorldSyncExt}, - sys::combat::{BLOCK_ANGLE, BLOCK_EFFICIENCY}, + sys::combat::BLOCK_ANGLE, terrain::{Block, TerrainGrid}, vol::{ReadVol, Vox}, }; @@ -159,9 +162,16 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) let state = &server.state; if vel.z <= -30.0 { if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { - let falldmg = vel.z.powi(2) as i32 / 20 - 40; + let falldmg = vel.z.powi(2) / 20.0 - 40.0; + let mut damage = Damage { + healthchange: -falldmg, + source: DamageSource::Falling, + }; + if let Some(loadout) = state.ecs().read_storage::().get(entity) { + damage.modify_damage(false, loadout); + } stats.health.change_by(comp::HealthChange { - amount: -falldmg, + amount: damage.healthchange as i32, cause: comp::HealthSource::World, }); } @@ -211,11 +221,12 @@ pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Opti // Go through all other entities let hit_range = 3.0 * power; let ecs = &server.state.ecs(); - for (pos_b, ori_b, character_b, stats_b) in ( + for (pos_b, ori_b, character_b, stats_b, loadout_b) in ( &ecs.read_storage::(), &ecs.read_storage::(), ecs.read_storage::().maybe(), &mut ecs.write_storage::(), + ecs.read_storage::().maybe(), ) .join() { @@ -227,21 +238,22 @@ pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Opti && distance_squared < hit_range.powi(2) { // Weapon gives base damage - let mut dmg = ((1.0 - distance_squared / hit_range.powi(2)) * power * 10.0) as u32; + let dmg = (1.0 - distance_squared / hit_range.powi(2)) * power * 10.0; - if rand::random() { - dmg += 1; - } + let mut damage = Damage { + healthchange: -dmg, + source: DamageSource::Explosion, + }; - // Block - if character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - && ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0 - { - dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32 + let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) + && ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; + + if let Some(loadout) = loadout_b { + damage.modify_damage(block, loadout); } stats_b.health.change_by(HealthChange { - amount: -(dmg as i32), + amount: damage.healthchange as i32, cause: HealthSource::Projectile { owner }, }); } diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index b33a170e0b..aebe689286 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -5,6 +5,7 @@ use common::{ slot::{self, Slot}, Pos, MAX_PICKUP_RANGE_SQR, }, + recipe::default_recipe_book, sync::{Uid, WorldSyncExt}, terrain::block::Block, vol::{ReadVol, Vox}, @@ -165,16 +166,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv thrown_items.push(( *pos, state - .ecs() - .read_storage::() - .get(entity) - .copied() + .read_component_cloned::(entity) .unwrap_or_default(), state - .ecs() - .read_storage::() - .get(entity) - .copied() + .read_component_cloned::(entity) .unwrap_or_default(), *kind, )); @@ -240,14 +235,13 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv }; if reinsert { - let _ = inventory.insert(slot, item); + let _ = inventory.insert_or_stack(slot, item); } Some(comp::InventoryUpdateEvent::Used) }, _ => { - // TODO: this doesn't work for stackable items - inventory.insert(slot, item).unwrap(); + inventory.insert_or_stack(slot, item).unwrap(); None }, } @@ -317,10 +311,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv dropped_items.push(( *pos, state - .ecs() - .read_storage::() - .get(entity) - .copied() + .read_component_cloned::(entity) .unwrap_or_default(), item, )); @@ -330,6 +321,39 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Dropped), ); }, + + comp::InventoryManip::CraftRecipe(recipe) => { + if let Some(inv) = state + .ecs() + .write_storage::() + .get_mut(entity) + { + let recipe_book = default_recipe_book(); + let craft_result = recipe_book.get(&recipe).and_then(|r| r.perform(inv).ok()); + + if craft_result.is_some() { + let _ = state.ecs().write_storage().insert( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Craft), + ); + } + + // Drop the item if there wasn't enough space + if let Some(Some((item, amount))) = craft_result { + for _ in 0..amount { + dropped_items.push(( + state + .read_component_cloned::(entity) + .unwrap_or_default(), + state + .read_component_cloned::(entity) + .unwrap_or_default(), + item.clone(), + )); + } + } + } + }, } // Drop items diff --git a/server/src/events/player.rs b/server/src/events/player.rs index aae303c47f..0e7bd7120d 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -1,6 +1,6 @@ use super::Event; use crate::{ - auth_provider::AuthProvider, client::Client, persistence, state_ext::StateExt, Server, + client::Client, login_provider::LoginProvider, persistence, state_ext::StateExt, Server, }; use common::{ comp, @@ -8,8 +8,9 @@ use common::{ msg::{ClientState, PlayerListUpdate, ServerMsg}, sync::{Uid, UidAllocator}, }; +use futures_executor::block_on; use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, WorldExt}; -use tracing::error; +use tracing::{debug, error, trace}; pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { let state = server.state_mut(); @@ -46,6 +47,17 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { } pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event { + if let Some(client) = server.state().read_storage::().get(entity) { + trace!("Closing participant of client"); + let participant = client.participant.lock().unwrap().take().unwrap(); + if let Err(e) = block_on(participant.disconnect()) { + debug!( + ?e, + "Error when disconnecting client, maybe the pipe already broke" + ); + }; + } + let state = server.state_mut(); // Tell other clients to remove from player list @@ -56,11 +68,11 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event state.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(*uid))) } - // Make sure to remove the player from the logged in list. (See AuthProvider) + // Make sure to remove the player from the logged in list. (See LoginProvider) // And send a disconnected message if let Some(player) = state.ecs().read_storage::().get(entity) { - let mut accounts = state.ecs().write_resource::(); - accounts.logout(player.uuid()); + let mut login_provider = state.ecs().write_resource::(); + login_provider.logout(player.uuid()); let msg = comp::ChatType::Offline.server_msg(format!("[{}] went offline.", &player.alias)); state.notify_registered_clients(msg); diff --git a/server/src/lib.rs b/server/src/lib.rs index 621a04ddeb..798bc7f0a3 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -2,13 +2,14 @@ #![allow(clippy::option_map_unit_fn)] #![feature(drain_filter, option_zip)] -pub mod auth_provider; +pub mod alias_validator; pub mod chunk_generator; pub mod client; pub mod cmd; pub mod error; pub mod events; pub mod input; +pub mod login_provider; pub mod metrics; pub mod persistence; pub mod settings; @@ -20,10 +21,11 @@ pub mod sys; pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings}; use crate::{ - auth_provider::AuthProvider, + alias_validator::AliasValidator, chunk_generator::ChunkGenerator, client::{Client, RegionSubscription}, cmd::ChatCommandExt, + login_provider::LoginProvider, state_ext::StateExt, sys::sentinel::{DeletedEntities, TrackedComps}, }; @@ -32,6 +34,7 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{server::WorldMapMsg, ClientState, ServerInfo, ServerMsg}, + recipe::default_recipe_book, state::{State, TimeOfDay}, sync::WorldSyncExt, terrain::TerrainChunkSize, @@ -41,7 +44,7 @@ use futures_executor::block_on; use futures_timer::Delay; use futures_util::{select, FutureExt}; use metrics::{ServerMetrics, TickMetrics}; -use network::{Address, Network, Pid}; +use network::{Network, Pid, ProtocolAddr}; use persistence::character::{CharacterLoader, CharacterLoaderResponseType, CharacterUpdater}; use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt}; use std::{ @@ -52,7 +55,7 @@ use std::{ }; #[cfg(not(feature = "worldgen"))] use test_world::{World, WORLD_SIZE}; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; use uvth::{ThreadPool, ThreadPoolBuilder}; use vek::*; #[cfg(feature = "worldgen")] @@ -96,10 +99,9 @@ impl Server { let mut state = State::default(); state.ecs_mut().insert(settings.clone()); state.ecs_mut().insert(EventBus::::default()); - state.ecs_mut().insert(AuthProvider::new( - settings.auth_server_address.clone(), - settings.whitelist.clone(), - )); + state + .ecs_mut() + .insert(LoginProvider::new(settings.auth_server_address.clone())); state.ecs_mut().insert(Tick(0)); state.ecs_mut().insert(ChunkGenerator::new()); state @@ -136,6 +138,37 @@ impl Server { state.ecs_mut().register::(); state.ecs_mut().register::(); + //Alias validator + let banned_words_paths = &settings.banned_words_files; + let mut banned_words = Vec::new(); + for path in banned_words_paths { + let mut list = match std::fs::File::open(&path) { + Ok(file) => match ron::de::from_reader(&file) { + Ok(vec) => vec, + Err(error) => { + tracing::warn!(?error, ?file, "Couldn't deserialize banned words file"); + return Err(Error::Other(format!( + "Couldn't read banned words file \"{}\"", + path.to_string_lossy() + ))); + }, + }, + Err(error) => { + tracing::warn!(?error, ?path, "Couldn't open banned words file"); + return Err(Error::Other(format!( + "Couldn't open banned words file \"{}\". Error: {}", + path.to_string_lossy(), + error + ))); + }, + }; + banned_words.append(&mut list); + } + let banned_words_count = banned_words.len(); + tracing::debug!(?banned_words_count); + tracing::trace!(?banned_words); + state.ecs_mut().insert(AliasValidator::new(banned_words)); + #[cfg(feature = "worldgen")] let world = World::generate(settings.world_seed, WorldOpts { seed_elements: true, @@ -244,9 +277,9 @@ impl Server { let thread_pool = ThreadPoolBuilder::new() .name("veloren-worker".to_string()) .build(); - let (network, f) = Network::new(Pid::new(), None); + let (network, f) = Network::new(Pid::new()); thread_pool.execute(f); - block_on(network.listen(Address::Tcp(settings.gameserver_address)))?; + block_on(network.listen(ProtocolAddr::Tcp(settings.gameserver_address)))?; let this = Self { state, @@ -347,13 +380,7 @@ impl Server { let before_new_connections = Instant::now(); // 3) Handle inputs from clients - block_on(async { - //TIMEOUT 0.01 ms for msg handling - select!( - _ = Delay::new(std::time::Duration::from_micros(10)).fuse() => Ok(()), - err = self.handle_new_connections(&mut frontend_events).fuse() => err, - ) - })?; + block_on(self.handle_new_connections(&mut frontend_events))?; let before_message_system = Instant::now(); @@ -600,13 +627,38 @@ impl Server { &mut self, frontend_events: &mut Vec, ) -> Result<(), Error> { + //TIMEOUT 0.1 ms for msg handling + const TIMEOUT: Duration = Duration::from_micros(100); loop { - let participant = self.network.connected().await?; - let singleton_stream = participant.opened().await?; + let participant = match select!( + _ = Delay::new(TIMEOUT).fuse() => None, + pr = self.network.connected().fuse() => Some(pr), + ) { + None => return Ok(()), + Some(pr) => pr?, + }; + debug!("New Participant connected to the server"); + + let singleton_stream = match select!( + _ = Delay::new(TIMEOUT*100).fuse() => None, + sr = participant.opened().fuse() => Some(sr), + ) { + None => { + warn!("Either Slowloris attack or very slow client, dropping"); + return Ok(()); //return rather then continue to give removes a tick more to send data. + }, + Some(Ok(s)) => s, + Some(Err(e)) => { + warn!(?e, "Failed to open a Stream from remote client. dropping"); + continue; + }, + }; let mut client = Client { client_state: ClientState::Connected, + participant: std::sync::Mutex::new(Some(participant)), singleton_stream, + network_error: std::sync::atomic::AtomicBool::new(false), last_ping: self.state.get_time(), login_msg_sent: false, }; @@ -638,10 +690,11 @@ impl Server { server_info: self.get_server_info(), time_of_day: *self.state.ecs().read_resource(), world_map: self.map.clone(), + recipe_book: (&*default_recipe_book()).clone(), }); - debug!("Done initial sync with client."); frontend_events.push(Event::ClientConnected { entity }); + debug!("Done initial sync with client."); } } } diff --git a/server/src/auth_provider.rs b/server/src/login_provider.rs similarity index 54% rename from server/src/auth_provider.rs rename to server/src/login_provider.rs index 9b2c944f61..0410f528c7 100644 --- a/server/src/auth_provider.rs +++ b/server/src/login_provider.rs @@ -15,33 +15,64 @@ fn derive_uuid(username: &str) -> Uuid { Uuid::from_slice(&state.to_be_bytes()).unwrap() } -pub struct AuthProvider { +pub struct LoginProvider { accounts: HashMap, auth_server: Option, - whitelist: Vec, } -impl AuthProvider { - pub fn new(auth_addr: Option, whitelist: Vec) -> Self { +impl LoginProvider { + pub fn new(auth_addr: Option) -> Self { let auth_server = match auth_addr { Some(addr) => Some(AuthClient::new(addr)), None => None, }; - AuthProvider { + Self { accounts: HashMap::new(), auth_server, - whitelist, } } + fn login(&mut self, uuid: Uuid, username: String) -> Result<(), RegisterError> { + // make sure that the user is not logged in already + if self.accounts.contains_key(&uuid) { + return Err(RegisterError::AlreadyLoggedIn); + } + info!(?username, "New User"); + self.accounts.insert(uuid, username); + Ok(()) + } + pub fn logout(&mut self, uuid: Uuid) { if self.accounts.remove(&uuid).is_none() { error!(?uuid, "Attempted to logout user that is not logged in."); }; } - pub fn query(&mut self, username_or_token: String) -> Result<(String, Uuid), RegisterError> { + pub fn try_login( + &mut self, + username_or_token: &str, + whitelist: &[String], + ) -> Result<(String, Uuid), RegisterError> { + self + // resolve user information + .query(username_or_token) + // if found, check name against whitelist or if user is admin + .and_then(|(username, uuid)| { + // user can only join if he is admin, the whitelist is empty (everyone can join) + // or his name is in the whitelist + if !whitelist.is_empty() && !whitelist.contains(&username) { + return Err(RegisterError::NotOnWhitelist); + } + + // add the user to self.accounts + self.login(uuid, username.clone())?; + + Ok((username, uuid)) + }) + } + + pub fn query(&mut self, username_or_token: &str) -> Result<(String, Uuid), RegisterError> { // Based on whether auth server is provided or not we expect an username or // token match &self.auth_server { @@ -49,36 +80,19 @@ impl AuthProvider { Some(srv) => { info!(?username_or_token, "Validating token"); // Parse token - let token = AuthToken::from_str(&username_or_token) + let token = AuthToken::from_str(username_or_token) .map_err(|e| RegisterError::AuthError(e.to_string()))?; // Validate token let uuid = srv.validate(token)?; - // Check if already logged in - if self.accounts.contains_key(&uuid) { - return Err(RegisterError::AlreadyLoggedIn); - } let username = srv.uuid_to_username(uuid)?; - // Check if player is in whitelist - if self.whitelist.len() > 0 && !self.whitelist.contains(&username) { - return Err(RegisterError::NotOnWhitelist); - } - - // Log in - self.accounts.insert(uuid, username.clone()); Ok((username, uuid)) }, // Username is expected None => { // Assume username was provided let username = username_or_token; - let uuid = derive_uuid(&username); - if !self.accounts.contains_key(&uuid) { - info!(?username, "New User"); - self.accounts.insert(uuid, username.clone()); - Ok((username, uuid)) - } else { - Err(RegisterError::AlreadyLoggedIn) - } + let uuid = derive_uuid(username); + Ok((username.to_string(), uuid)) }, } } diff --git a/server/src/metrics.rs b/server/src/metrics.rs index 3fcf94cf4f..11913abd7a 100644 --- a/server/src/metrics.rs +++ b/server/src/metrics.rs @@ -33,7 +33,6 @@ pub struct ServerMetrics { } impl TickMetrics { - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 pub fn new(registry: &Registry, tick: Arc) -> Result> { let player_online = IntGauge::with_opts(Opts::new( "player_online", @@ -65,10 +64,10 @@ impl TickMetrics { "chunks_count", "number of all chunks currently active on the server", ))?; - let tick_time = IntGaugeVec::from(IntGaugeVec::new( + let tick_time = IntGaugeVec::new( Opts::new("tick_time", "time in ns requiered for a tick of the server"), &["period"], - )?); + )?; let since_the_epoch = SystemTime::now() .duration_since(UNIX_EPOCH) diff --git a/server/src/migrations/2020-07-16-044718_migrate_armour_stats/down.sql b/server/src/migrations/2020-07-16-044718_migrate_armour_stats/down.sql new file mode 100644 index 0000000000..c1733c05f6 --- /dev/null +++ b/server/src/migrations/2020-07-16-044718_migrate_armour_stats/down.sql @@ -0,0 +1 @@ +-- No down action for this migration \ No newline at end of file diff --git a/server/src/migrations/2020-07-16-044718_migrate_armour_stats/up.sql b/server/src/migrations/2020-07-16-044718_migrate_armour_stats/up.sql new file mode 100644 index 0000000000..b55f5eec50 --- /dev/null +++ b/server/src/migrations/2020-07-16-044718_migrate_armour_stats/up.sql @@ -0,0 +1,29 @@ +-- This migration updates all "stats" fields for each armour item in player loadouts +UPDATE + loadout +SET + items = json_replace( + items, + '$.back.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.belt.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.chest.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.foot.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.hand.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.head.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.neck.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.pants.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.ring.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.shoulder.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }'), + '$.tabard.kind.Armor.stats', + json('{ "protection": { "Normal": 1.0 } }') + ); \ No newline at end of file diff --git a/server/src/migrations/2020-07-19-223917_update_item_stats/down.sql b/server/src/migrations/2020-07-19-223917_update_item_stats/down.sql new file mode 100644 index 0000000000..291a97c5ce --- /dev/null +++ b/server/src/migrations/2020-07-19-223917_update_item_stats/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/server/src/migrations/2020-07-19-223917_update_item_stats/up.sql b/server/src/migrations/2020-07-19-223917_update_item_stats/up.sql new file mode 100644 index 0000000000..a747646b1d --- /dev/null +++ b/server/src/migrations/2020-07-19-223917_update_item_stats/up.sql @@ -0,0 +1,39 @@ +-- This migration updates all "stats" fields for each armour item in player inventory. +UPDATE + inventory +SET + items = json_replace( + -- Replace inventory slots. + items, + '$.slots', + ( + -- Replace each item in the inventory, by splitting the json into an array, applying our changes, + -- and then re-aggregating. + -- + -- NOTE: SQLite does not seem to provide a way to guarantee the order is the same after aggregation! + -- For now, it *does* seem to order by slots.key, but this doesn't seem to be guaranteed by anything. + -- For explicitness, we still include the ORDER BY, even though it seems to have no effect. + SELECT json_group_array( + json_replace( + slots.value, + '$.kind.Armor.stats', + CASE + -- ONLY replace item stats when the stats field currently lacks "protection" + -- (NOTE: This will also return true if the value is null, so if you are creating a nullable + -- JSON field please be careful before rerunning this migration!). + WHEN json_extract(slots.value, '$.kind.Armor.stats.protection') IS NULL + THEN + -- Replace armor stats with new armor + json('{ "protection": { "Normal": 1.0 } }') + ELSE + -- The protection stat was already added. + json_extract(slots.value, '$.kind.Armor.stats') + END + ) + ) + -- Extract all item slots + FROM json_each(json_extract(items, '$.slots')) AS slots + ORDER BY slots.key + ) + ) +; diff --git a/server/src/migrations/2020-07-24-191205_fix_projectile_stats/down.sql b/server/src/migrations/2020-07-24-191205_fix_projectile_stats/down.sql new file mode 100644 index 0000000000..291a97c5ce --- /dev/null +++ b/server/src/migrations/2020-07-24-191205_fix_projectile_stats/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/server/src/migrations/2020-07-24-191205_fix_projectile_stats/up.sql b/server/src/migrations/2020-07-24-191205_fix_projectile_stats/up.sql new file mode 100644 index 0000000000..2bf12adfb9 --- /dev/null +++ b/server/src/migrations/2020-07-24-191205_fix_projectile_stats/up.sql @@ -0,0 +1,15 @@ +-- This migration adjusts projectiles in accordance with the changes made in MR 1222 +UPDATE + loadout +SET + items = json_replace( + items, + '$.active_item.ability1.BasicRanged.projectile.hit_entity[0]', + json('{"Damage": -3}'), + '$.active_item.ability2.BasicRanged.projectile.hit_entity[0]', + json('{"Damage": -3}'), + '$.second_item.ability1.BasicRanged.projectile.hit_entity[0]', + json('{"Damage": -3}'), + '$.second_item.ability2.BasicRanged.projectile.hit_entity[0]', + json('{"Damage": -3}') + ); \ No newline at end of file diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index 34c1d4377a..441d741c63 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -22,6 +22,9 @@ use tracing::warn; // See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html // This macro is called at build-time, and produces the necessary migration info // for the `embedded_migrations` call below. +// +// NOTE: Adding a useless comment to trigger the migrations being run. Delete +// when needed. embed_migrations!(); /// Runs any pending database migrations. This is executed during server startup diff --git a/server/src/settings.rs b/server/src/settings.rs index 9e0d0f637a..8bc0c6f3a5 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -25,6 +25,7 @@ pub struct ServerSettings { pub map_file: Option, pub persistence_db_dir: String, pub max_view_distance: Option, + pub banned_words_files: Vec, } impl Default for ServerSettings { @@ -63,6 +64,7 @@ impl Default for ServerSettings { whitelist: Vec::new(), persistence_db_dir: "saves".to_owned(), max_view_distance: Some(30), + banned_words_files: Vec::new(), } } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 3ea09a0003..d2d7c895d6 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -1,7 +1,7 @@ use super::SysTimer; use crate::{ - auth_provider::AuthProvider, client::Client, persistence::character::CharacterLoader, - ServerSettings, CLIENT_TIMEOUT, + alias_validator::AliasValidator, client::Client, login_provider::LoginProvider, + persistence::character::CharacterLoader, ServerSettings, CLIENT_TIMEOUT, }; use common::{ comp::{ @@ -27,8 +27,8 @@ use specs::{ }; impl Sys { - ///We need to move this to a async fn, otherwise the compiler generates to - /// much recursive fn, and async closures dont work yet + ///We needed to move this to a async fn, if we would use a async closures + /// the compiler generates to much recursion and fails to compile this #[allow(clippy::too_many_arguments)] async fn handle_client_msg( server_emitter: &mut common::event::Emitter<'_, ServerEvent>, @@ -45,7 +45,7 @@ impl Sys { force_updates: &ReadStorage<'_, ForceUpdate>, stats: &mut WriteStorage<'_, Stats>, chat_modes: &ReadStorage<'_, ChatMode>, - accounts: &mut WriteExpect<'_, AuthProvider>, + login_provider: &mut WriteExpect<'_, LoginProvider>, block_changes: &mut Write<'_, BlockChange>, admin_list: &ReadExpect<'_, AdminList>, admins: &mut WriteStorage<'_, Admin>, @@ -55,9 +55,10 @@ impl Sys { players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, ServerSettings>, + alias_validator: &ReadExpect<'_, AliasValidator>, ) -> Result<(), crate::error::Error> { loop { - let msg = client.singleton_stream.recv().await?; + let msg = client.recv().await?; *cnt += 1; match msg { // Go back to registered state (char selection screen) @@ -85,13 +86,14 @@ impl Sys { view_distance, token_or_username, } => { - let (username, uuid) = match accounts.query(token_or_username.clone()) { - Err(err) => { - client.error_state(RequestStateError::RegisterDenied(err)); - break Ok(()); - }, - Ok((username, uuid)) => (username, uuid), - }; + let (username, uuid) = + match login_provider.try_login(&token_or_username, &settings.whitelist) { + Err(err) => { + client.error_state(RequestStateError::RegisterDenied(err)); + break Ok(()); + }, + Ok((username, uuid)) => (username, uuid), + }; let vd = view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); @@ -146,20 +148,20 @@ impl Sys { }, ClientMsg::SetViewDistance(view_distance) => { if let ClientState::Character { .. } = client.client_state { + players.get_mut(entity).map(|player| { + player.view_distance = Some( + settings + .max_view_distance + .map(|max| view_distance.min(max)) + .unwrap_or(view_distance), + ) + }); + if settings .max_view_distance - .map(|max| view_distance <= max) - .unwrap_or(true) + .map(|max| view_distance > max) + .unwrap_or(false) { - players.get_mut(entity).map(|player| { - player.view_distance = Some( - settings - .max_view_distance - .map(|max| view_distance.min(max)) - .unwrap_or(view_distance), - ) - }); - } else { client.notify(ServerMsg::SetViewDistance( settings.max_view_distance.unwrap_or(0), )); @@ -316,7 +318,8 @@ impl Sys { pos.0.xy().map(|e| e as f64).distance( key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64), - ) < (view_distance as f64 + 1.5) * TerrainChunkSize::RECT_SIZE.x as f64 + ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) + * TerrainChunkSize::RECT_SIZE.x as f64 } else { true }; @@ -347,7 +350,14 @@ impl Sys { } }, ClientMsg::CreateCharacter { alias, tool, body } => { - if let Some(player) = players.get(entity) { + if let Err(error) = alias_validator.validate(&alias) { + tracing::debug!( + ?error, + ?alias, + "denied alias as it contained a banned word" + ); + client.notify(ServerMsg::CharacterActionError(error.to_string())); + } else if let Some(player) = players.get(entity) { character_loader.create_character( entity, player.uuid().to_string(), @@ -402,7 +412,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, ForceUpdate>, WriteStorage<'a, Stats>, ReadStorage<'a, ChatMode>, - WriteExpect<'a, AuthProvider>, + WriteExpect<'a, LoginProvider>, Write<'a, BlockChange>, ReadExpect<'a, AdminList>, WriteStorage<'a, Admin>, @@ -413,6 +423,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Client>, WriteStorage<'a, Controller>, Read<'a, ServerSettings>, + ReadExpect<'a, AliasValidator>, ); #[allow(clippy::match_ref_pats)] // TODO: Pending review in #587 @@ -443,6 +454,7 @@ impl<'a> System<'a> for Sys { mut clients, mut controllers, settings, + alias_validator, ): Self::SystemData, ) { timer.start(); @@ -473,9 +485,9 @@ impl<'a> System<'a> for Sys { let mut cnt = 0; let network_err: Result<(), crate::error::Error> = block_on(async { - //TIMEOUT 0.01 ms for msg handling + //TIMEOUT 0.02 ms for msg handling select!( - _ = Delay::new(std::time::Duration::from_micros(10)).fuse() => Ok(()), + _ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()), err = Self::handle_client_msg( &mut server_emitter, &mut new_chat_msgs, @@ -502,6 +514,7 @@ impl<'a> System<'a> for Sys { &mut players, &mut controllers, &settings, + &alias_validator, ).fuse() => err, ) }); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 4c2e9e976f..e28727675d 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -208,7 +208,9 @@ impl<'a> System<'a> for Sys { foot: Some(assets::load_expect_cloned( "common.items.armor.foot.cultist_boots", )), - back: None, + back: Some(assets::load_expect_cloned( + "common.items.armor.back.dungeon_purple-0", + )), ring: None, neck: None, lantern: Some(assets::load_expect_cloned("common.items.lantern.black_0")), diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 95da79dbc4..702cd1b039 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -26,11 +26,11 @@ anim = { package = "veloren-voxygen-anim", path = "src/anim", default-features = gfx = "0.18.2" gfx_device_gl = { version = "0.16.2", optional = true } gfx_gl = { version = "0.6.1", optional = true } -gfx_window_glutin = "0.31.0" -glutin = "0.21.1" -winit = { version = "0.19.4", features = ["serde"] } -conrod_core = { git = "https://gitlab.com/veloren/conrod.git", branch = "pre-winit-20" } -conrod_winit = { git = "https://gitlab.com/veloren/conrod.git", branch = "pre-winit-20" } +old_school_gfx_glutin_ext = "0.24" +glutin = "0.24.1" +winit = { version = "0.22.2", features = ["serde"] } +conrod_core = { git = "https://gitlab.com/veloren/conrod.git" } +conrod_winit = { git = "https://gitlab.com/veloren/conrod.git" } euc = { git = "https://github.com/zesterer/euc.git" } # ECS @@ -91,7 +91,6 @@ winres = "0.1" criterion = "0.3" git2 = "0.13" world = { package = "veloren-world", path = "../world" } -gfx_window_glutin = { version = "0.31.0", features = ["headless"] } [[bench]] name = "meshing_benchmark" diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs index cbd0b1b80a..aa02b414f4 100644 --- a/voxygen/examples/character_renderer.rs +++ b/voxygen/examples/character_renderer.rs @@ -1,9 +1,16 @@ -use common::comp; +// TODO: Fix this example when we switch to actively maintained rendering +// backend. Right now we would have to update `gfx_window_glutin` to work with +// the latest version of glutin or we would need to add headless support to +// `old_school_gfx_glutin_ext`. + +fn main() { + println!("Example temporarily disabled, see the TODO comment for details"); +} +/*use common::comp; use gfx_window_glutin::init_headless; use vek::*; use veloren_voxygen::{render, scene::simple as scene}; -#[allow(clippy::clone_on_copy)] // TODO: Pending review in #587 fn main() { // Setup renderer let dim = (200u16, 300u16, 1, gfx::texture::AaMode::Single); @@ -64,4 +71,4 @@ fn main() { // Get image let img = renderer.create_screenshot().unwrap(); img.save("character.png").unwrap(); -} +}*/ diff --git a/voxygen/examples/voxygen - Shortcut.lnk b/voxygen/examples/voxygen - Shortcut.lnk deleted file mode 100644 index 722dd67f2a..0000000000 Binary files a/voxygen/examples/voxygen - Shortcut.lnk and /dev/null differ diff --git a/voxygen/src/anim/character/glidewield.rs b/voxygen/src/anim/character/glidewield.rs deleted file mode 100644 index 0b0985e64a..0000000000 --- a/voxygen/src/anim/character/glidewield.rs +++ /dev/null @@ -1,304 +0,0 @@ -use super::{super::Animation, CharacterSkeleton, SkeletonAttr}; -use common::comp::item::ToolKind; -use std::{f32::consts::PI, ops::Mul}; -use vek::*; - -pub struct GlideWieldAnimation; - -impl Animation for GlideWieldAnimation { - type Dependency = (Option, Option, Vec3, Vec3, Vec3, f64); - type Skeleton = CharacterSkeleton; - - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 - fn update_skeleton( - skeleton: &Self::Skeleton, - (_active_tool_kind, _second_tool_kind, velocity, orientation, last_ori, global_time): Self::Dependency, - anim_time: f64, - rate: &mut f32, - skeleton_attr: &SkeletonAttr, - ) -> Self::Skeleton { - let mut next = (*skeleton).clone(); - let speed = Vec2::::from(velocity).magnitude(); - *rate = 1.0; - let slow = (anim_time as f32 * 1.0).sin(); - let breathe = ((anim_time as f32 * 0.5).sin()).abs(); - let walkintensity = if speed > 5.0 { 1.0 } else { 0.45 }; - let walk = if speed > 5.0 { 1.0 } else { 0.5 }; - let lower = if speed > 5.0 { 0.0 } else { 1.0 }; - let _snapfoot = if speed > 5.0 { 1.1 } else { 2.0 }; - let lab = 1.0; - let foothoril = (anim_time as f32 * 16.0 * walk * lab as f32 + PI * 1.45).sin(); - let foothorir = (anim_time as f32 * 16.0 * walk * lab as f32 + PI * (0.45)).sin(); - - let footvertl = (anim_time as f32 * 16.0 * walk * lab as f32).sin(); - let footvertr = (anim_time as f32 * 16.0 * walk * lab as f32 + PI).sin(); - - let footrotl = (((5.0) - / (2.5 - + (2.5) - * ((anim_time as f32 * 16.0 * walk * lab as f32 + PI * 1.4).sin()) - .powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * 16.0 * walk * lab as f32 + PI * 1.4).sin()); - - let footrotr = (((5.0) - / (1.0 - + (4.0) - * ((anim_time as f32 * 16.0 * walk * lab as f32 + PI * 0.4).sin()) - .powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * 16.0 * walk * lab as f32 + PI * 0.4).sin()); - - let short = (((5.0) - / (1.5 - + 3.5 * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()).powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()); - let noisea = (anim_time as f32 * 11.0 + PI / 6.0).sin(); - let noiseb = (anim_time as f32 * 19.0 + PI / 4.0).sin(); - - let shorte = (((5.0) - / (4.0 - + 1.0 * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()).powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()); - - let shortalt = (anim_time as f32 * lab as f32 * 16.0 * walk + PI / 2.0).sin(); - let shortalter = (anim_time as f32 * lab as f32 * 16.0 * walk + PI / -2.0).sin(); - - let wave_stop = (anim_time as f32 * 26.0).min(PI / 2.0 / 2.0).sin(); - - let head_look = Vec2::new( - ((global_time + anim_time) as f32 / 18.0) - .floor() - .mul(7331.0) - .sin() - * 0.2, - ((global_time + anim_time) as f32 / 18.0) - .floor() - .mul(1337.0) - .sin() - * 0.1, - ); - - let ori = Vec2::from(orientation); - let last_ori = Vec2::from(last_ori); - let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) - .map(|m| m > 0.001 && m.is_finite()) - .reduce_and() - && ori.angle_between(last_ori).is_finite() - { - ori.angle_between(last_ori).min(0.2) - * last_ori.determine_side(Vec2::zero(), ori).signum() - } else { - 0.0 - } * 1.3; - - next.l_hand.offset = Vec3::new( - -2.0 - skeleton_attr.hand.0, - skeleton_attr.hand.1, - skeleton_attr.hand.2 + 15.0, - ); - next.l_hand.ori = Quaternion::rotation_x(3.35); - next.l_hand.scale = Vec3::one(); - - next.r_hand.offset = Vec3::new( - 2.0 + skeleton_attr.hand.0, - skeleton_attr.hand.1, - skeleton_attr.hand.2 + 15.0, - ); - next.r_hand.ori = Quaternion::rotation_x(3.35); - next.r_hand.scale = Vec3::one(); - - if speed > 0.5 { - next.head.offset = Vec3::new( - 0.0, - -3.0 + skeleton_attr.head.0, - skeleton_attr.head.1 + short * 0.1, - ); - next.head.ori = Quaternion::rotation_z(tilt * -2.5 + head_look.x * 0.2 - short * 0.1) - * Quaternion::rotation_x(head_look.y + 0.45 - lower * 0.35); - next.head.scale = Vec3::one() * skeleton_attr.head_scale; - - next.chest.offset = Vec3::new( - 0.0, - skeleton_attr.chest.0, - skeleton_attr.chest.1 + 2.0 + shortalt * -1.5 - lower, - ); - next.chest.ori = Quaternion::rotation_z(short * 0.10 * walkintensity + tilt * -1.0) - * Quaternion::rotation_y(tilt * 2.2) - * Quaternion::rotation_x( - shortalter * 0.035 + wave_stop * speed * -0.1 + (tilt.abs()), - ); - next.chest.scale = Vec3::one(); - - next.belt.offset = Vec3::new(0.0, skeleton_attr.belt.0, skeleton_attr.belt.1); - next.belt.ori = Quaternion::rotation_z(short * 0.1 + tilt * -1.1) - * Quaternion::rotation_y(tilt * 0.5); - next.belt.scale = Vec3::one(); - - next.glider.ori = Quaternion::rotation_x(0.8); - next.glider.offset = Vec3::new(0.0, -10.0, 15.0); - next.glider.scale = Vec3::one() * 1.0; - - next.back.offset = Vec3::new(0.0, skeleton_attr.back.0, skeleton_attr.back.1); - next.back.ori = - Quaternion::rotation_x(-0.25 + short * 0.1 + noisea * 0.1 + noiseb * 0.1); - next.back.scale = Vec3::one() * 1.02; - - next.shorts.offset = Vec3::new(0.0, skeleton_attr.shorts.0, skeleton_attr.shorts.1); - next.shorts.ori = Quaternion::rotation_z(short * 0.25 + tilt * -1.5) - * Quaternion::rotation_y(tilt * 0.7); - next.shorts.scale = Vec3::one(); - - next.l_foot.offset = Vec3::new( - -skeleton_attr.foot.0, - -1.5 + skeleton_attr.foot.1 + foothoril * -8.5 * walkintensity - lower * 1.0, - 2.0 + skeleton_attr.foot.2 + ((footvertl * -2.7).max(-1.0)) * walkintensity, - ); - next.l_foot.ori = Quaternion::rotation_x(-0.2 + footrotl * -1.2 * walkintensity) - * Quaternion::rotation_y(tilt * 1.8); - next.l_foot.scale = Vec3::one(); - - next.r_foot.offset = Vec3::new( - skeleton_attr.foot.0, - -1.5 + skeleton_attr.foot.1 + foothorir * -8.5 * walkintensity - lower * 1.0, - 2.0 + skeleton_attr.foot.2 + ((footvertr * -2.7).max(-1.0)) * walkintensity, - ); - next.r_foot.ori = Quaternion::rotation_x(-0.2 + footrotr * -1.2 * walkintensity) - * Quaternion::rotation_y(tilt * 1.8); - next.r_foot.scale = Vec3::one(); - - next.l_shoulder.offset = Vec3::new( - -skeleton_attr.shoulder.0, - skeleton_attr.shoulder.1, - skeleton_attr.shoulder.2, - ); - next.l_shoulder.ori = Quaternion::rotation_x(short * 0.15 * walkintensity); - next.l_shoulder.scale = Vec3::one() * 1.1; - - next.r_shoulder.offset = Vec3::new( - skeleton_attr.shoulder.0, - skeleton_attr.shoulder.1, - skeleton_attr.shoulder.2, - ); - next.r_shoulder.ori = Quaternion::rotation_x(short * -0.15 * walkintensity); - next.r_shoulder.scale = Vec3::one() * 1.1; - - next.main.offset = Vec3::new(-7.0, -6.5, 15.0); - next.main.ori = - Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + short * 0.25); - next.main.scale = Vec3::one(); - - next.second.scale = Vec3::one(); - - next.lantern.offset = Vec3::new( - skeleton_attr.lantern.0, - skeleton_attr.lantern.1, - skeleton_attr.lantern.2, - ); - next.lantern.ori = - Quaternion::rotation_x(shorte * 0.7 + 0.4) * Quaternion::rotation_y(shorte * 0.4); - next.lantern.scale = Vec3::one() * 0.65; - - next.torso.offset = Vec3::new(0.0, 0.0, 0.0) * skeleton_attr.scaler; - next.torso.ori = Quaternion::rotation_y(0.0); - next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; - - next.control.offset = Vec3::new(0.0, 0.0, 0.0); - next.control.ori = Quaternion::rotation_x(0.0); - next.control.scale = Vec3::one(); - - next.l_control.scale = Vec3::one(); - - next.r_control.scale = Vec3::one(); - } else { - next.head.offset = Vec3::new( - 0.0, - -3.0 + skeleton_attr.head.0, - skeleton_attr.head.1 + slow * 0.3 + breathe * -0.05, - ); - next.head.ori = - Quaternion::rotation_z(head_look.x) * Quaternion::rotation_x(head_look.y.abs()); - next.head.scale = Vec3::one() * skeleton_attr.head_scale + breathe * -0.05; - - next.chest.offset = Vec3::new( - 0.0, - skeleton_attr.chest.0, - skeleton_attr.chest.1 + slow * 0.3, - ); - next.chest.ori = Quaternion::rotation_z(head_look.x * 0.6); - next.chest.scale = Vec3::one() * 1.01 + breathe * 0.03; - - next.belt.offset = Vec3::new(0.0, skeleton_attr.belt.0, skeleton_attr.belt.1); - next.belt.ori = Quaternion::rotation_z(head_look.x * -0.1); - next.belt.scale = Vec3::one() + breathe * -0.03; - - next.glider.ori = Quaternion::rotation_x(0.35); - next.glider.offset = Vec3::new(0.0, -9.0, 17.0); - next.glider.scale = Vec3::one() * 1.0; - - next.back.offset = Vec3::new(0.0, skeleton_attr.back.0, skeleton_attr.back.1); - next.back.scale = Vec3::one() * 1.02; - - next.shorts.offset = Vec3::new(0.0, skeleton_attr.shorts.0, skeleton_attr.shorts.1); - next.shorts.ori = Quaternion::rotation_z(head_look.x * -0.2); - next.shorts.scale = Vec3::one() + breathe * -0.03; - - next.l_foot.offset = Vec3::new( - -skeleton_attr.foot.0, - skeleton_attr.foot.1, - skeleton_attr.foot.2, - ); - next.l_foot.scale = Vec3::one(); - - next.r_foot.offset = Vec3::new( - skeleton_attr.foot.0, - skeleton_attr.foot.1, - skeleton_attr.foot.2, - ); - next.r_foot.scale = Vec3::one(); - - next.l_shoulder.offset = Vec3::new( - -skeleton_attr.shoulder.0, - skeleton_attr.shoulder.1, - skeleton_attr.shoulder.2, - ); - next.l_shoulder.scale = (Vec3::one() + breathe * -0.05) * 1.15; - - next.r_shoulder.offset = Vec3::new( - skeleton_attr.shoulder.0, - skeleton_attr.shoulder.1, - skeleton_attr.shoulder.2, - ); - next.r_shoulder.scale = (Vec3::one() + breathe * -0.05) * 1.15; - - next.main.offset = Vec3::new(-7.0, -5.0, 15.0); - next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one(); - - next.second.offset = Vec3::new(0.0, 0.0, 0.0); - next.second.scale = Vec3::one(); - - next.lantern.offset = Vec3::new( - skeleton_attr.lantern.0, - skeleton_attr.lantern.1, - skeleton_attr.lantern.2, - ); - next.lantern.ori = Quaternion::rotation_x(0.1) * Quaternion::rotation_y(0.1); - next.lantern.scale = Vec3::one() * 0.65; - - next.torso.offset = Vec3::new(0.0, 0.0, 0.0) * skeleton_attr.scaler; - next.torso.ori = Quaternion::rotation_x(0.0); - next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; - - next.control.scale = Vec3::one(); - - next.l_control.scale = Vec3::one(); - - next.r_control.scale = Vec3::one(); - } - next - } -} diff --git a/voxygen/src/anim/src/character/alpha.rs b/voxygen/src/anim/src/character/alpha.rs index ecaca5f4c9..56823da136 100644 --- a/voxygen/src/anim/src/character/alpha.rs +++ b/voxygen/src/anim/src/character/alpha.rs @@ -359,7 +359,8 @@ impl Animation for AlphaAnimation { Some(ToolKind::Staff(_)) => { next.head.offset = Vec3::new( 0.0, - 0.0 + skeleton_attr.head.0 + decel * 0.8, + 0.0 + skeleton_attr.head.0, /* + decel * 0.8 */ + // Had some clipping issues skeleton_attr.head.1, ); next.head.ori = Quaternion::rotation_z(decel * 0.25) diff --git a/voxygen/src/anim/src/character/charge.rs b/voxygen/src/anim/src/character/charge.rs index 0563243364..e2581c3e2b 100644 --- a/voxygen/src/anim/src/character/charge.rs +++ b/voxygen/src/anim/src/character/charge.rs @@ -21,7 +21,7 @@ impl Animation for ChargeAnimation { #[cfg_attr(feature = "be-dyn-lib", export_name = "character_charge")] #[allow(clippy::approx_constant)] // TODO: Pending review in #587 - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, velocity, orientation, last_ori, _global_time): Self::Dependency, @@ -60,10 +60,10 @@ impl Animation for ChargeAnimation { let stop = ((anim_time as f32).powf(0.3 as f32)).min(1.2); let stopa = ((anim_time as f32).powf(0.9 as f32)).min(5.0); - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/character/glidewield.rs b/voxygen/src/anim/src/character/glidewield.rs index 70646f2f95..c60a985410 100644 --- a/voxygen/src/anim/src/character/glidewield.rs +++ b/voxygen/src/anim/src/character/glidewield.rs @@ -22,7 +22,7 @@ impl Animation for GlideWieldAnimation { const UPDATE_FN: &'static [u8] = b"character_glidewield\0"; #[cfg_attr(feature = "be-dyn-lib", export_name = "character_glidewield")] - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, velocity, orientation, last_ori, global_time): Self::Dependency, @@ -94,10 +94,10 @@ impl Animation for GlideWieldAnimation { * 0.1, ); - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/character/gliding.rs b/voxygen/src/anim/src/character/gliding.rs index 704523b333..615d7d0a1d 100644 --- a/voxygen/src/anim/src/character/gliding.rs +++ b/voxygen/src/anim/src/character/gliding.rs @@ -22,7 +22,7 @@ impl Animation for GlidingAnimation { const UPDATE_FN: &'static [u8] = b"character_gliding\0"; #[cfg_attr(feature = "be-dyn-lib", export_name = "character_gliding")] - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, velocity, orientation, last_ori, global_time): Self::Dependency, @@ -54,10 +54,10 @@ impl Animation for GlidingAnimation { * 0.25, ); - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.0001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/character/idle.rs b/voxygen/src/anim/src/character/idle.rs index 4a73cede6a..e7d612ca65 100644 --- a/voxygen/src/anim/src/character/idle.rs +++ b/voxygen/src/anim/src/character/idle.rs @@ -57,6 +57,9 @@ impl Animation for IdleAnimation { next.shorts.ori = Quaternion::rotation_x(0.0); next.shorts.scale = Vec3::one(); + next.back.offset = Vec3::new(0.0, skeleton_attr.back.0, skeleton_attr.back.1); + next.back.scale = Vec3::one() * 1.02; + next.l_hand.offset = Vec3::new( -skeleton_attr.hand.0, skeleton_attr.hand.1 + wave_ultra_slow_cos * 0.15, diff --git a/voxygen/src/anim/src/character/jump.rs b/voxygen/src/anim/src/character/jump.rs index 33730731c1..62cccf8941 100644 --- a/voxygen/src/anim/src/character/jump.rs +++ b/voxygen/src/anim/src/character/jump.rs @@ -18,7 +18,7 @@ impl Animation for JumpAnimation { const UPDATE_FN: &'static [u8] = b"character_jump\0"; #[cfg_attr(feature = "be-dyn-lib", export_name = "character_jump")] - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, orientation, last_ori, global_time): Self::Dependency, @@ -39,10 +39,10 @@ impl Animation for JumpAnimation { let switch = if random > 0.5 { 1.0 } else { -1.0 }; - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/character/roll.rs b/voxygen/src/anim/src/character/roll.rs index 7a31246219..5079d119c7 100644 --- a/voxygen/src/anim/src/character/roll.rs +++ b/voxygen/src/anim/src/character/roll.rs @@ -19,7 +19,7 @@ impl Animation for RollAnimation { const UPDATE_FN: &'static [u8] = b"character_roll\0"; #[cfg_attr(feature = "be-dyn-lib", export_name = "character_roll")] - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, orientation, last_ori, _global_time): Self::Dependency, @@ -31,10 +31,10 @@ impl Animation for RollAnimation { let mut next = (*skeleton).clone(); let spin = anim_time as f32; - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.0001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/character/run.rs b/voxygen/src/anim/src/character/run.rs index 35bd0cacdd..bb64344d39 100644 --- a/voxygen/src/anim/src/character/run.rs +++ b/voxygen/src/anim/src/character/run.rs @@ -23,7 +23,7 @@ impl Animation for RunAnimation { const UPDATE_FN: &'static [u8] = b"character_run\0"; #[cfg_attr(feature = "be-dyn-lib", export_name = "character_run")] - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, velocity, orientation, last_ori, global_time, avg_vel): Self::Dependency, @@ -35,7 +35,7 @@ impl Animation for RunAnimation { let speed = Vec2::::from(velocity).magnitude(); *rate = 1.0; - let impact = (avg_vel.z * 3000.0).max(-8.0); + let impact = (avg_vel.z).max(-8.0); let walkintensity = if speed > 5.0 { 1.0 } else { 0.45 }; let walk = if speed > 5.0 { 1.0 } else { 0.5 }; @@ -96,10 +96,10 @@ impl Animation for RunAnimation { * 0.1, ); - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/character/stand.rs b/voxygen/src/anim/src/character/stand.rs index a117e6c6b2..96bccada22 100644 --- a/voxygen/src/anim/src/character/stand.rs +++ b/voxygen/src/anim/src/character/stand.rs @@ -24,7 +24,7 @@ impl Animation for StandAnimation { let slow = (anim_time as f32 * 1.0).sin(); let breathe = ((anim_time as f32 * 0.5).sin()).abs(); - let impact = (avg_vel.z * 3000.0).max(-15.0); + let impact = (avg_vel.z).max(-15.0); let head_look = Vec2::new( ((global_time + anim_time) as f32 / 12.0) .floor() diff --git a/voxygen/src/anim/src/character/swim.rs b/voxygen/src/anim/src/character/swim.rs index 12e9be25d3..cbabf7c081 100644 --- a/voxygen/src/anim/src/character/swim.rs +++ b/voxygen/src/anim/src/character/swim.rs @@ -22,7 +22,7 @@ impl Animation for SwimAnimation { const UPDATE_FN: &'static [u8] = b"character_swim\0"; #[cfg_attr(feature = "be-dyn-lib", export_name = "character_swim")] - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 + fn update_skeleton_inner( skeleton: &Self::Skeleton, (active_tool_kind, second_tool_kind, velocity, orientation, last_ori, global_time): Self::Dependency, @@ -57,10 +57,10 @@ impl Animation for SwimAnimation { .sin() * 0.1, ); - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/quadruped_low/run.rs b/voxygen/src/anim/src/quadruped_low/run.rs index 0a69bbfc30..259d660929 100644 --- a/voxygen/src/anim/src/quadruped_low/run.rs +++ b/voxygen/src/anim/src/quadruped_low/run.rs @@ -11,7 +11,6 @@ impl Animation for RunAnimation { #[cfg(feature = "use-dyn-lib")] const UPDATE_FN: &'static [u8] = b"quadruped_low_run\0"; - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 #[cfg_attr(feature = "be-dyn-lib", export_name = "quadruped_low_run")] fn update_skeleton_inner( skeleton: &Self::Skeleton, @@ -71,10 +70,10 @@ impl Animation for RunAnimation { * ((anim_time as f32 * 16.0 * lab as f32 + PI * 0.05).sin()); let footvertrb = (anim_time as f32 * 16.0 * lab as f32 + PI * 0.6).sin(); - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/anim/src/quadruped_medium/run.rs b/voxygen/src/anim/src/quadruped_medium/run.rs index 5feef6b718..5dadddf37d 100644 --- a/voxygen/src/anim/src/quadruped_medium/run.rs +++ b/voxygen/src/anim/src/quadruped_medium/run.rs @@ -11,7 +11,6 @@ impl Animation for RunAnimation { #[cfg(feature = "use-dyn-lib")] const UPDATE_FN: &'static [u8] = b"quadruped_medium_run\0"; - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 #[cfg_attr(feature = "be-dyn-lib", export_name = "quadruped_medium_run")] fn update_skeleton_inner( skeleton: &Self::Skeleton, @@ -58,10 +57,10 @@ impl Animation for RunAnimation { let footvertaltfslow = (anim_time as f32 * 16.0 * lab as f32 * speedmult + PI * 1.8).sin(); let footverttaltfslow = (anim_time as f32 * 16.0 * lab as f32 * speedmult + PI * 2.2).sin(); // - let ori = Vec2::from(orientation); + let ori: Vec2 = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); let tilt = if Vec2::new(ori, last_ori) - .map(|o| Vec2::::from(o).magnitude_squared()) + .map(|o| o.magnitude_squared()) .map(|m| m > 0.001 && m.is_finite()) .reduce_and() && ori.angle_between(last_ori).is_finite() diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 7e38e47cf5..0af0b8b244 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -2,6 +2,7 @@ use super::{ img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager}, + util::loadout_slot_text, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ @@ -19,6 +20,7 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; + use vek::Vec2; widget_ids! { @@ -30,7 +32,6 @@ widget_ids! { inv_grid_2, inv_scrollbar, inv_slots_0, - map_title, inv_slots[], //tooltip[], bg, @@ -77,6 +78,7 @@ widget_ids! { end_ico, fit_ico, wp_ico, + prot_ico, } } @@ -340,180 +342,139 @@ impl<'a> Widget for Bag<'a> { image_source: self.item_imgs, slot_manager: Some(self.slot_manager), }; + let i18n = &self.localized_strings; // Head - let (title, desc) = loadout - .head - .as_ref() - .map_or((self.localized_strings.get("hud.bag.head"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.head.as_ref(), || (i18n.get("hud.bag.head"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Head), [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.0) .with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.head_slot, ui); // Necklace - let (title, desc) = loadout - .neck - .as_ref() - .map_or((self.localized_strings.get("hud.bag.neck"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.neck.as_ref(), || (i18n.get("hud.bag.neck"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Neck), [45.0; 2]) .mid_bottom_with_margin_on(state.ids.head_slot, -55.0) .with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.neck_slot, ui); // Chest //Image::new(self.imgs.armor_slot) // different graphics for empty/non empty - let (title, desc) = loadout - .chest - .as_ref() - .map_or((self.localized_strings.get("hud.bag.chest"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.chest.as_ref(), || (i18n.get("hud.bag.chest"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Chest), [85.0; 2]) .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0) .with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.chest_slot, ui); // Shoulders - let (title, desc) = loadout.shoulder.as_ref().map_or( - (self.localized_strings.get("hud.bag.shoulders"), ""), - |item| (item.name(), item.description()), - ); + let (title, desc) = loadout_slot_text(loadout.shoulder.as_ref(), || { + (i18n.get("hud.bag.shoulders"), "") + }); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Shoulders), [70.0; 2]) .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0) .with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.shoulders_slot, ui); // Hands - let (title, desc) = loadout - .hand - .as_ref() - .map_or((self.localized_strings.get("hud.bag.hands"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.hand.as_ref(), || (i18n.get("hud.bag.hands"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Hands), [70.0; 2]) .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0) .with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.hands_slot, ui); // Belt - let (title, desc) = loadout - .belt - .as_ref() - .map_or((self.localized_strings.get("hud.bag.belt"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.belt.as_ref(), || (i18n.get("hud.bag.belt"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Belt), [45.0; 2]) .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0) .with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.belt_slot, ui); // Legs - let (title, desc) = loadout - .pants - .as_ref() - .map_or((self.localized_strings.get("hud.bag.legs"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.pants.as_ref(), || (i18n.get("hud.bag.legs"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Legs), [85.0; 2]) .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0) .with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.legs_slot, ui); // Lantern - let (title, desc) = loadout.lantern.as_ref().map_or( - (self.localized_strings.get("hud.bag.lantern"), ""), - |item| (item.name(), item.description()), - ); + let (title, desc) = loadout_slot_text(loadout.lantern.as_ref(), || { + (i18n.get("hud.bag.lantern"), "") + }); slot_maker .fabricate(EquipSlot::Lantern, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) .with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.lantern_slot, ui); // Ring - let (title, desc) = loadout - .ring - .as_ref() - .map_or((self.localized_strings.get("hud.bag.ring"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.ring.as_ref(), || (i18n.get("hud.bag.ring"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Ring), [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.ring_slot, ui); // Back - let (title, desc) = loadout - .back - .as_ref() - .map_or((self.localized_strings.get("hud.bag.back"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.back.as_ref(), || (i18n.get("hud.bag.back"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Back), [45.0; 2]) .down_from(state.ids.lantern_slot, 10.0) .with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.back_slot, ui); // Foot - let (title, desc) = loadout - .foot - .as_ref() - .map_or((self.localized_strings.get("hud.bag.feet"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.foot.as_ref(), || (i18n.get("hud.bag.feet"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Feet), [45.0; 2]) .down_from(state.ids.ring_slot, 10.0) .with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.feet_slot, ui); // Tabard - let (title, desc) = loadout - .tabard - .as_ref() - .map_or((self.localized_strings.get("hud.bag.tabard"), ""), |item| { - (item.name(), item.description()) - }); + let (title, desc) = + loadout_slot_text(loadout.tabard.as_ref(), || (i18n.get("hud.bag.tabard"), "")); slot_maker .fabricate(EquipSlot::Armor(ArmorSlot::Tabard), [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) .with_icon(self.imgs.tabard_bg, Vec2::new(60.0, 60.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.tabard_slot, ui); // Mainhand/Left-Slot - let (title, desc) = loadout.active_item.as_ref().map(|i| &i.item).map_or( - (self.localized_strings.get("hud.bag.mainhand"), ""), - |item| (item.name(), item.description()), - ); + let (title, desc) = + loadout_slot_text(loadout.active_item.as_ref().map(|i| &i.item), || { + (i18n.get("hud.bag.mainhand"), "") + }); slot_maker .fabricate(EquipSlot::Mainhand, [85.0; 2]) .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0) .with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.mainhand_slot, ui); // Offhand/Right-Slot - let (title, desc) = loadout.second_item.as_ref().map(|i| &i.item).map_or( - (self.localized_strings.get("hud.bag.offhand"), ""), - |item| (item.name(), item.description()), - ); + let (title, desc) = + loadout_slot_text(loadout.second_item.as_ref().map(|i| &i.item), || { + (i18n.get("hud.bag.offhand"), "") + }); slot_maker .fabricate(EquipSlot::Offhand, [85.0; 2]) .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0) .with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN)) - .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.offhand_slot, ui); } else { // Stats @@ -581,11 +542,14 @@ impl<'a> Widget for Bag<'a> { // Divider /*Image::new(self.imgs.divider) .w_h(50.0, 5.0) - .mid_top_with_margin_on(state.ids.exp, 30.0) + .mid_top_with_margin_on(state.ids.exp, 20.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.divider, ui);*/ // Stats + // Defense + let damage_reduction = (100.0 * loadout.get_damage_reduction()) as i32; + Text::new( &self .localized_strings @@ -597,24 +561,29 @@ impl<'a> Widget for Bag<'a> { .color(TEXT_COLOR) .set(state.ids.statnames, ui); Image::new(self.imgs.endurance_ico) - .w_h(30.0, 30.0) - .top_left_with_margins_on(state.ids.statnames, -10.0, -40.0) + .w_h(20.0, 20.0) + .top_left_with_margins_on(state.ids.statnames, 0.0, -40.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.end_ico, ui); Image::new(self.imgs.fitness_ico) - .w_h(30.0, 30.0) - .down_from(state.ids.end_ico, 10.0) + .w_h(20.0, 20.0) + .down_from(state.ids.end_ico, 15.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.fit_ico, ui); Image::new(self.imgs.willpower_ico) - .w_h(30.0, 30.0) - .down_from(state.ids.fit_ico, 10.0) + .w_h(20.0, 20.0) + .down_from(state.ids.fit_ico, 15.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.wp_ico, ui); + Image::new(self.imgs.protection_ico) + .w_h(20.0, 20.0) + .down_from(state.ids.wp_ico, 15.0) + .color(Some(UI_HIGHLIGHT_0)) + .set(state.ids.prot_ico, ui); Text::new(&format!( - "{}\n\n{}\n\n{}", - self.stats.endurance, self.stats.fitness, self.stats.willpower + "{}\n\n{}\n\n{}\n\n{}%", + self.stats.endurance, self.stats.fitness, self.stats.willpower, damage_reduction )) .top_right_with_margins_on(state.ids.stats_alignment, 120.0, 150.0) .font_id(self.fonts.cyri.conrod_id) @@ -663,16 +632,9 @@ impl<'a> Widget for Bag<'a> { 0.0 + x as f64 * (40.0), ); if let Some(item) = item { + let (title, desc) = super::util::item_text(item); slot_widget - .with_tooltip( - self.tooltip_manager, - &item.name(), - &format!( - "{}", - /* item.kind, item.effect(), */ item.description() - ), - &item_tooltip, - ) + .with_tooltip(self.tooltip_manager, title, &*desc, &item_tooltip) .set(state.ids.inv_slots[i], ui); } else { slot_widget.set(state.ids.inv_slots[i], ui); diff --git a/voxygen/src/hud/buttons.rs b/voxygen/src/hud/buttons.rs index 167aa0f117..e1925a065e 100644 --- a/voxygen/src/hud/buttons.rs +++ b/voxygen/src/hud/buttons.rs @@ -37,6 +37,11 @@ widget_ids! { spellbook_button_bg, spellbook_text, spellbook_text_bg, + crafting_button, + crafting_button_bg, + crafting_text, + crafting_text_bg, + } } const TOOLTIP_UPSHIFT: f64 = 40.0; @@ -93,6 +98,7 @@ pub enum Event { ToggleMap, ToggleSocial, ToggleSpell, + ToggleCrafting, } impl<'a> Widget for Buttons<'a> { @@ -360,6 +366,43 @@ impl<'a> Widget for Buttons<'a> { .color(TEXT_COLOR) .set(state.ids.spellbook_text, ui); } + // Crafting + if Button::image(self.imgs.crafting_icon) + .w_h(25.0, 25.0) + .left_from(state.ids.spellbook_button, 10.0) + .hover_image(self.imgs.crafting_icon_hover) + .press_image(self.imgs.crafting_icon_press) + .with_tooltip( + self.tooltip_manager, + &localized_strings.get("hud.crafting"), + "", + &button_tooltip, + ) + .bottom_offset(TOOLTIP_UPSHIFT) + .set(state.ids.crafting_button, ui) + .was_clicked() + { + return Some(Event::ToggleCrafting); + } + if let Some(crafting) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Crafting) + { + Text::new(crafting.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.crafting_button, 0.0, 0.0) + .font_size(10) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.crafting_text_bg, ui); + Text::new(crafting.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.crafting_text_bg, 1.0, 1.0) + .font_size(10) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.crafting_text, ui); + } None } } diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 675f9622d6..46267014ad 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -30,6 +30,8 @@ widget_ids! { chat_icons[], } } +/*#[const_tweaker::tweak(min = 0.0, max = 60.0, step = 1.0)] +const X: f64 = 18.0;*/ const MAX_MESSAGES: usize = 100; @@ -316,7 +318,7 @@ impl<'a> Widget for Chat<'a> { .set(state.ids.message_box_bg, ui); let (mut items, _) = List::flow_down(state.messages.len() + 1) .top_left_with_margins_on(state.ids.message_box_bg, 0.0, 16.0) - .w_h(CHAT_BOX_WIDTH, CHAT_BOX_HEIGHT) + .w_h(CHAT_BOX_WIDTH - 16.0, CHAT_BOX_HEIGHT) .scroll_kids_vertically() .set(state.ids.message_box, ui); if state.ids.chat_icons.len() < state.messages.len() { @@ -337,7 +339,7 @@ impl<'a> Widget for Chat<'a> { let text = Text::new(&msg) .font_size(self.fonts.opensans.scale(15)) .font_id(self.fonts.opensans.conrod_id) - .w(CHAT_BOX_WIDTH - 16.0) + .w(CHAT_BOX_WIDTH - 17.0) .color(color) .line_spacing(2.0); // Add space between messages. diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs new file mode 100644 index 0000000000..19fff3dfa6 --- /dev/null +++ b/voxygen/src/hud/crafting.rs @@ -0,0 +1,505 @@ +use super::{ + img_ids::{Imgs, ImgsRot}, + item_imgs::ItemImgs, + TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN, +}; +use crate::{ + i18n::VoxygenLocalization, + ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, +}; +use client::{self, Client}; +use common::comp::Inventory; +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Scrollbar, Text}, + widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + pub struct Ids { + window, + window_frame, + close, + icon, + title_main, + title_rec, + align_rec, + scrollbar_rec, + title_ing, + align_ing, + scrollbar_ing, + btn_craft, + recipe_names[], + recipe_img_frame[], + recipe_img[], + ingredients[], + ingredient_frame[], + ingredient_img[], + req_text[], + ingredients_txt, + output_img_frame, + output_img, + } +} + +pub enum Event { + CraftRecipe(String), + Close, +} + +#[derive(WidgetCommon)] +pub struct Crafting<'a> { + client: &'a Client, + imgs: &'a Imgs, + fonts: &'a ConrodVoxygenFonts, + localized_strings: &'a std::sync::Arc, + rot_imgs: &'a ImgsRot, + tooltip_manager: &'a mut TooltipManager, + item_imgs: &'a ItemImgs, + inventory: &'a Inventory, + #[conrod(common_builder)] + common: widget::CommonBuilder, +} +#[allow(clippy::too_many_arguments)] +impl<'a> Crafting<'a> { + pub fn new( + client: &'a Client, + imgs: &'a Imgs, + fonts: &'a ConrodVoxygenFonts, + localized_strings: &'a std::sync::Arc, + rot_imgs: &'a ImgsRot, + tooltip_manager: &'a mut TooltipManager, + item_imgs: &'a ItemImgs, + inventory: &'a Inventory, + ) -> Self { + Self { + client, + imgs, + fonts, + localized_strings, + rot_imgs, + tooltip_manager, + item_imgs, + inventory, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, + selected_recipe: Option, +} + +impl<'a> Widget for Crafting<'a> { + type Event = Vec; + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + selected_recipe: None, + } + } + + #[allow(clippy::unused_unit)] // TODO: Pending review in #587 + fn style(&self) -> Self::Style { () } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + + if state.ids.recipe_names.len() < self.client.recipe_book().iter().len() { + state.update(|state| { + state.ids.recipe_names.resize( + self.client.recipe_book().iter().len(), + &mut ui.widget_id_generator(), + ) + }); + } + /*if state.ids.recipe_img_frame.len() < self.client.recipe_book().iter().len() { + state.update(|state| { + state.ids.recipe_img_frame.resize( + self.client.recipe_book().iter().len(), + &mut ui.widget_id_generator(), + ) + }); + } + if state.ids.recipe_img.len() < self.client.recipe_book().iter().len() { + state.update(|state| { + state.ids.recipe_img.resize( + self.client.recipe_book().iter().len(), + &mut ui.widget_id_generator(), + ) + }); + }*/ + + let ids = &state.ids; + + let mut events = Vec::new(); + + // Tooltips + let item_tooltip = Tooltip::new({ + let edge = &self.rot_imgs.tt_side; + let corner = &self.rot_imgs.tt_corner; + ImageFrame::new( + [edge.cw180, edge.none, edge.cw270, edge.cw90], + [corner.none, corner.cw270, corner.cw90, corner.cw180], + Color::Rgba(0.08, 0.07, 0.04, 1.0), + 5.0, + ) + }) + .title_font_size(self.fonts.cyri.scale(15)) + .parent(ui.window) + .desc_font_size(self.fonts.cyri.scale(12)) + .title_text_color(TEXT_COLOR) + .font_id(self.fonts.cyri.conrod_id) + .desc_text_color(TEXT_COLOR); + + Image::new(self.imgs.crafting_window) + .bottom_right_with_margins_on(ui.window, 308.0, 450.0) + .color(Some(UI_MAIN)) + .w_h(422.0, 460.0) + .set(ids.window, ui); + Image::new(self.imgs.crafting_frame) + .middle_of(ids.window) + .color(Some(UI_HIGHLIGHT_0)) + .w_h(422.0, 460.0) + .set(ids.window_frame, ui); + Image::new(self.imgs.crafting_icon_bordered) + .w_h(38.0, 38.0) + .top_left_with_margins_on(state.ids.window_frame, 4.0, 4.0) + .set(state.ids.icon, ui); + // Close Button + if Button::image(self.imgs.close_button) + .w_h(24.0, 25.0) + .hover_image(self.imgs.close_button_hover) + .press_image(self.imgs.close_button_press) + .top_right_with_margins_on(ids.window, 0.0, 0.0) + .set(ids.close, ui) + .was_clicked() + { + events.push(Event::Close); + } + + // Title + Text::new(&self.localized_strings.get("hud.crafting")) + .mid_top_with_margin_on(ids.window_frame, 9.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(22)) + .color(TEXT_COLOR) + .set(ids.title_main, ui); + + // Alignment + Rectangle::fill_with([136.0, 378.0], color::TRANSPARENT) + .top_left_with_margins_on(ids.window_frame, 74.0, 5.0) + .set(ids.align_rec, ui); + Rectangle::fill_with([274.0, 340.0], color::TRANSPARENT) + .top_right_with_margins_on(ids.window, 74.0, 5.0) + .set(ids.align_ing, ui); + let client = &self.client; + // First available recipes, then unavailable ones + let recipe_iter = self + .client + .recipe_book() + .iter() + .filter(|(name, _)| client.available_recipes().contains(name.as_str())) + .map(|(name, recipe)| (name, recipe, true)) + .chain( + client + .recipe_book() + .iter() + .filter(|(name, _)| !client.available_recipes().contains(name.as_str())) + .map(|(name, recipe)| (name, recipe, false)), + ); + match &state.selected_recipe { + None => {}, + Some(recipe) => { + let can_perform = client.available_recipes().contains(recipe.as_str()); + // Ingredients Text + Text::new(&self.localized_strings.get("hud.crafting.ingredients")) + .top_left_with_margins_on(state.ids.align_ing, 10.0, 5.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)) + .color(TEXT_COLOR) + .set(state.ids.ingredients_txt, ui); + // Craft button + if Button::image(self.imgs.button) + .w_h(105.0, 25.0) + .hover_image( + can_perform + .then_some(self.imgs.button_hover) + .unwrap_or(self.imgs.button), + ) + .press_image( + can_perform + .then_some(self.imgs.button_press) + .unwrap_or(self.imgs.button), + ) + .label(&self.localized_strings.get("hud.crafting.craft")) + .label_y(conrod_core::position::Relative::Scalar(1.0)) + .label_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR)) + .label_font_size(self.fonts.cyri.scale(12)) + .label_font_id(self.fonts.cyri.conrod_id) + .image_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR)) + .mid_bottom_with_margin_on(ids.align_ing, -31.0) + .set(ids.btn_craft, ui) + .was_clicked() + { + events.push(Event::CraftRecipe(recipe.clone())); + } + // Result Image BG + Image::new(self.imgs.inv_slot) + .w_h(60.0, 60.0) + .top_right_with_margins_on(state.ids.align_ing, 15.0, 10.0) + .parent(ids.align_ing) + .set(ids.output_img_frame, ui); + + if let Some(recipe) = state + .selected_recipe + .as_ref() + .and_then(|r| self.client.recipe_book().get(r.as_str())) + { + let output_text = format!("x{}", &recipe.output.1.to_string()); + // Output Image + Button::image( + self.item_imgs + .img_id_or_not_found_img((&recipe.output.0).into()), + ) + .w_h(55.0, 55.0) + .middle_of(state.ids.output_img_frame) + .label(if recipe.output.1 > 1 {&output_text} else {""}) // Only show output amount for amounts > 1 + .label_color(TEXT_COLOR) + .label_font_size(self.fonts.cyri.scale(14)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_y(conrod_core::position::Relative::Scalar(-24.0)) + .label_x(conrod_core::position::Relative::Scalar(24.0)) + .with_tooltip( + self.tooltip_manager, + recipe.output.0.name(), + recipe.output.0.description(), + &item_tooltip, + ) + .set(state.ids.output_img, ui); + } + }, + } + + // Recipe list + for (i, (name, recipe, can_perform)) in recipe_iter.enumerate() { + let button = Button::image( + if state + .selected_recipe + .as_ref() + .map(|s| s != name) + .unwrap_or(false) + { + self.imgs.nothing + } else { + match state.selected_recipe { + None => self.imgs.nothing, + Some(_) => self.imgs.selection, + } + }, + ); + // Recipe Button + let button = if i == 0 { + button.mid_top_with_margin_on(state.ids.align_rec, 2.0) + } else { + button.mid_bottom_with_margin_on(state.ids.recipe_names[i - 1], -25.0) + }; + if button + .label(recipe.output.0.name()) + .w_h(130.0, 20.0) + .hover_image(self.imgs.selection_hover) + .press_image(self.imgs.selection_press) + .label_color(can_perform.then_some(TEXT_COLOR).unwrap_or(TEXT_GRAY_COLOR)) + .label_font_size(self.fonts.cyri.scale(12)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_y(conrod_core::position::Relative::Scalar(2.0)) + .set(state.ids.recipe_names[i], ui) + .was_clicked() + { + if state + .selected_recipe + .as_ref() + .map(|s| s == name) + .unwrap_or(false) + { + state.update(|s| s.selected_recipe = None); + } else { + state.update(|s| s.selected_recipe = Some(name.clone())); + } + } + // Image BG + /*Rectangle::fill_with([10.0, 10.0], color::TRANSPARENT) + .w_h(20.0, 20.0) + .mid_left_of(state.ids.recipe_names[i]) + .set(state.ids.recipe_img_frame[i], ui); + //Item Image + Image::new( + self.item_imgs + .img_id_or_not_found_img((&recipe.output.0).into()), + ) + .w_h(18.0, 18.0) + .color( + can_perform + .then_some(Some(TEXT_COLOR)) + .unwrap_or(Some(TEXT_GRAY_COLOR)), + ) + .middle_of(state.ids.recipe_img_frame[i]) + .set(state.ids.recipe_img[i], ui);*/ + } + + //Ingredients + if let Some(recipe) = state + .selected_recipe + .as_ref() + .and_then(|r| self.client.recipe_book().get(r.as_str())) + { + // Title + Text::new(&recipe.output.0.name().to_string()) + .mid_top_with_margin_on(state.ids.align_ing, -22.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(TEXT_COLOR) + .parent(state.ids.window) + .set(state.ids.title_ing, ui); + // Ingredient images with tooltip + if state.ids.ingredient_frame.len() < recipe.inputs().len() { + state.update(|state| { + state + .ids + .ingredient_frame + .resize(recipe.inputs().len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.ingredients.len() < recipe.inputs().len() { + state.update(|state| { + state + .ids + .ingredients + .resize(recipe.inputs().len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.ingredient_img.len() < recipe.inputs().len() { + state.update(|state| { + state + .ids + .ingredient_img + .resize(recipe.inputs().len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.req_text.len() < recipe.inputs().len() { + state.update(|state| { + state + .ids + .req_text + .resize(recipe.inputs().len(), &mut ui.widget_id_generator()) + }); + }; + // Widget generation for every ingredient + for (i, (item, amount)) in recipe.inputs.iter().enumerate() { + // Grey color for images and text if their amount is too low to craft the item + let col = if self.inventory.item_count(item) as f32 / *amount as f32 >= 1.0 { + TEXT_COLOR + } else { + TEXT_DULL_RED_COLOR + }; + // Slot BG + let frame_pos = if i == 0 { + state.ids.ingredients_txt + } else { + state.ids.ingredient_frame[i - 1] + }; + // add a larger offset for the the first ingredient and the "Required Text for + // Catalysts/Tools" + let frame_offset = if i == 0 { + 10.0 + } else if *amount == 0 { + 5.0 + } else { + 0.0 + }; + let frame = Image::new(self.imgs.inv_slot).w_h(25.0, 25.0); + let frame = if *amount == 0 { + frame.down_from(state.ids.req_text[i], 10.0 + frame_offset) + } else { + frame.down_from(frame_pos, 10.0 + frame_offset) + }; + frame.set(state.ids.ingredient_frame[i], ui); + //Item Image + let title = item.name(); + let desc = item.description(); + Button::image(self.item_imgs.img_id_or_not_found_img(item.into())) + .w_h(22.0, 22.0) + .middle_of(state.ids.ingredient_frame[i]) + //.image_color(col) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.ingredient_img[i], ui); + // Ingredients text and amount + // Don't show inventory amounts above 999 to avoid the widget clipping + let over9k = "999+"; + let in_inv: &str = &self.inventory.item_count(item).to_string(); + // Show Ingredients + // Align "Required" Text below last ingredient + if *amount == 0 { + // Catalysts/Tools + Text::new(&self.localized_strings.get("hud.crafting.tool_cata")) + .down_from(state.ids.ingredient_frame[i - 1], 20.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(col) + .set(state.ids.req_text[i], ui); + Text::new(item.name()) + .right_from(state.ids.ingredient_frame[i], 10.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(col) + .set(state.ids.ingredients[i], ui); + } else { + // Ingredients + let input = format!( + "{}x {} ({})", + amount, + item.name(), + if self.inventory.item_count(item) > 999 { + over9k + } else { + in_inv + } + ); + // Ingredient Text + Text::new(&input) + .right_from(state.ids.ingredient_frame[i], 10.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(col) + .set(state.ids.ingredients[i], ui); + } + } + } + + let ids = &state.ids; + // Scrollbars + Scrollbar::y_axis(ids.align_rec) + .thickness(5.0) + .rgba(0.33, 0.33, 0.33, 1.0) + .set(ids.scrollbar_rec, ui); + Scrollbar::y_axis(ids.align_ing) + .thickness(5.0) + .rgba(0.33, 0.33, 0.33, 1.0) + .set(ids.scrollbar_ing, ui); + + // Title Recipes and Ingredients + Text::new(&self.localized_strings.get("hud.crafting.recipes")) + .mid_top_with_margin_on(ids.align_rec, -22.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .color(TEXT_COLOR) + .parent(ids.window) + .set(ids.title_rec, ui); + + events + } +} diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 50fff76512..240860dd6e 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -1,16 +1,15 @@ -use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelPixArtGraphic}; +use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelPixArtGraphic}; // TODO: Combine with image_ids, see macro definition rotation_image_ids! { pub struct ImgsRot { - // Tooltip Test - tt_side: "voxygen/element/frames/tt_test_edge", - tt_corner: "voxygen/element/frames/tt_test_corner_tr", - indicator_mmap_small: "voxygen.element.buttons.indicator_mmap_small", + // Tooltip Test + tt_side: "voxygen/element/frames/tt_test_edge", + tt_corner: "voxygen/element/frames/tt_test_corner_tr", ////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -54,6 +53,10 @@ image_ids! { ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Selection Frame + selection: "voxygen.element.frames.selection", + selection_hover: "voxygen.element.frames.selection_hover", + selection_press: "voxygen.element.frames.selection_press", // Social Window social_button: "voxygen.element.buttons.social_tab", @@ -62,6 +65,16 @@ image_ids! { social_button_press: "voxygen.element.buttons.social_tab_press", social_frame: "voxygen.element.frames.social_frame", + // Crafting Window + + crafting_window: "voxygen.element.misc_bg.crafting", + crafting_frame: "voxygen.element.misc_bg.crafting_frame", + crafting_icon_bordered: "voxygen.element.icons.anvil", + crafting_icon: "voxygen.element.buttons.anvil", + crafting_icon_hover: "voxygen.element.buttons.anvil_hover", + crafting_icon_press: "voxygen.element.buttons.anvil_press", + + // Chat-Arrows chat_arrow: "voxygen.element.buttons.arrow_down", chat_arrow_mo: "voxygen.element.buttons.arrow_down_hover", @@ -115,7 +128,7 @@ image_ids! { staff_m2: "voxygen.element.icons.staff_m2", flyingrod_m1: "voxygen.element.icons.debug_wand_m1", flyingrod_m2: "voxygen.element.icons.debug_wand_m2", - charge: "voxygen.element.icons.skill_charge_3", + sword_pierce: "voxygen.element.icons.skill_sword_pierce", hammerleap: "voxygen.element.icons.skill_hammerleap", axespin: "voxygen.element.icons.skill_axespin", @@ -228,6 +241,7 @@ image_ids! { willpower_ico: "voxygen.element.icons.willpower", endurance_ico: "voxygen.element.icons.endurance", fitness_ico: "voxygen.element.icons.fitness", + protection_ico: "voxygen.element.icons.protection", not_found:"voxygen.element.not_found", diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 8661e911fe..53f55d1b1f 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -2,7 +2,7 @@ use crate::ui::{Graphic, SampleStrat, Transform, Ui}; use common::{ assets::{self, watch::ReloadIndicator, Asset}, comp::item::{ - armor::Armor, + armor::{Armor, ArmorKind}, tool::{Tool, ToolKind}, Consumable, Ingredient, Item, ItemKind, Lantern, LanternKind, Throwable, Utility, }, @@ -21,7 +21,7 @@ use vek::*; pub enum ItemKey { Tool(ToolKind), Lantern(LanternKind), - Armor(Armor), + Armor(ArmorKind), Utility(Utility), Consumable(Consumable), Throwable(Throwable), @@ -33,7 +33,7 @@ impl From<&Item> for ItemKey { match &item.kind { ItemKind::Tool(Tool { kind, .. }) => ItemKey::Tool(*kind), ItemKind::Lantern(Lantern { kind, .. }) => ItemKey::Lantern(*kind), - ItemKind::Armor { kind, .. } => ItemKey::Armor(*kind), + ItemKind::Armor(Armor { kind, .. }) => ItemKey::Armor(*kind), ItemKind::Utility { kind, .. } => ItemKey::Utility(*kind), ItemKind::Consumable { kind, .. } => ItemKey::Consumable(*kind), ItemKind::Throwable { kind, .. } => ItemKey::Throwable(*kind), diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index 3a0225539b..28d27563e7 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -8,7 +8,7 @@ use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use conrod_core::{ color, position, widget::{self, Button, Image, Rectangle, Text}, - widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; use specs::WorldExt; use vek::*; @@ -23,7 +23,11 @@ widget_ids! { mmap_plus, mmap_minus, grid, - indicator + indicator, + mmap_north, + mmap_east, + mmap_south, + mmap_west, } } @@ -39,6 +43,7 @@ pub struct MiniMap<'a> { fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, + ori: Vec3, } impl<'a> MiniMap<'a> { @@ -49,6 +54,7 @@ impl<'a> MiniMap<'a> { rot_imgs: &'a ImgsRot, world_map: &'a (img_ids::Rotations, Vec2), fonts: &'a ConrodVoxygenFonts, + ori: Vec3, ) -> Self { Self { show, @@ -58,6 +64,7 @@ impl<'a> MiniMap<'a> { world_map, fonts, common: widget::CommonBuilder::default(), + ori, } } } @@ -102,7 +109,7 @@ impl<'a> Widget for MiniMap<'a> { if self.show.mini_map { Image::new(self.imgs.mmap_frame) .w_h(174.0 * SCALE, 190.0 * SCALE) - .top_right_with_margins_on(ui.window, 0.0, 5.0) + .top_right_with_margins_on(ui.window, 5.0, 5.0) .color(Some(UI_MAIN)) .set(state.ids.mmap_frame, ui); Image::new(self.imgs.mmap_frame_2) @@ -195,10 +202,12 @@ impl<'a> Widget for MiniMap<'a> { [w_src, h_src], ); + let map_size = Vec2::new(170.0, 170.0); + // Map Image Image::new(world_map.source_north) .middle_of(state.ids.mmap_frame_bg) - .w_h(170.0 * SCALE, 170.0 * SCALE) + .w_h(map_size.x * SCALE, map_size.y * SCALE) .parent(state.ids.mmap_frame_bg) .source_rectangle(rect_src) .set(state.ids.grid, ui); @@ -212,6 +221,37 @@ impl<'a> Widget for MiniMap<'a> { .floating(true) .parent(ui.window) .set(state.ids.indicator, ui); + + // Compass directions + let dirs = [ + (Vec2::new(0.0, 1.0), state.ids.mmap_north, "N", true), + (Vec2::new(1.0, 0.0), state.ids.mmap_east, "E", false), + (Vec2::new(0.0, -1.0), state.ids.mmap_south, "S", false), + (Vec2::new(-1.0, 0.0), state.ids.mmap_west, "W", false), + ]; + for (dir, id, name, bold) in dirs.iter() { + let cardinal_dir = Vec2::unit_x().rotated_z(self.ori.x as f64) * dir.x + + Vec2::unit_y().rotated_z(self.ori.x as f64) * dir.y; + let clamped = (cardinal_dir * 3.0) + / (cardinal_dir * 3.0).map(|e| e.abs()).reduce_partial_max(); + let pos = clamped * (map_size * 0.73 - 10.0); + Text::new(name) + .x_y_position_relative_to( + state.ids.grid, + position::Relative::Scalar(pos.x), + position::Relative::Scalar(pos.y), + ) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) + .color(if *bold { + Color::Rgba(0.75, 0.0, 0.0, 1.0) + } else { + TEXT_COLOR + }) + .floating(true) + .parent(ui.window) + .set(*id, ui); + } } else { Image::new(self.imgs.mmap_frame_closed) .w_h(174.0 * SCALE, 18.0 * SCALE) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index fae0504015..5cb2ba9014 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1,6 +1,7 @@ mod bag; mod buttons; mod chat; +mod crafting; mod esc_menu; mod hotbar; mod img_ids; @@ -14,6 +15,7 @@ mod skillbar; mod slots; mod social; mod spell; +mod util; use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot, ui::img_ids::Rotations}; pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState}; @@ -25,6 +27,7 @@ use bag::Bag; use buttons::Buttons; use chat::Chat; use chrono::NaiveTime; +use crafting::Crafting; use esc_menu::EscMenu; use img_ids::Imgs; use item_imgs::ItemImgs; @@ -62,11 +65,14 @@ use vek::*; const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0); const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); +const TEXT_GRAY_COLOR: Color = Color::Rgba(0.5, 0.5, 0.5, 1.0); +const TEXT_DULL_RED_COLOR: Color = Color::Rgba(0.56, 0.2, 0.2, 1.0); const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); //const TEXT_COLOR_GREY: Color = Color::Rgba(1.0, 1.0, 1.0, 0.5); const MENU_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 0.4); //const TEXT_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); const TEXT_COLOR_3: Color = Color::Rgba(1.0, 1.0, 1.0, 0.1); +const TEXT_BIND_CONFLICT_COLOR: Color = Color::Rgba(1.0, 0.0, 0.0, 1.0); const BLACK: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); //const BG_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 0.8); const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0); @@ -198,6 +204,7 @@ widget_ids! { esc_menu, small_window, social_window, + crafting_window, settings_window, // Free look indicator @@ -237,6 +244,7 @@ pub struct HudInfo { } pub enum Event { + ToggleTips(bool), SendMessage(String), AdjustMousePan(u32), AdjustMouseZoom(u32), @@ -281,10 +289,12 @@ pub enum Event { Quit, ChangeLanguage(LanguageMetadata), ChangeBinding(GameInput), + ResetBindings, ChangeFreeLookBehavior(PressBehavior), ChangeRenderMode(RenderMode), ChangeAutoWalkBehavior(PressBehavior), ChangeStopAutoWalkOnInput(bool), + CraftRecipe(String), } // TODO: Are these the possible layouts we want? @@ -335,6 +345,7 @@ pub struct Show { ui: bool, intro: bool, help: bool, + crafting: bool, debug: bool, bag: bool, social: bool, @@ -353,29 +364,50 @@ pub struct Show { } impl Show { fn bag(&mut self, open: bool) { - self.bag = open; - self.map = false; - self.want_grab = !open; + if !self.esc_menu { + self.bag = open; + self.map = false; + self.want_grab = !open; + } } fn toggle_bag(&mut self) { self.bag(!self.bag); } fn map(&mut self, open: bool) { - self.map = open; - self.bag = false; - self.want_grab = !open; + if !self.esc_menu { + self.map = open; + self.bag = false; + self.crafting = false; + self.social = false; + self.spell = false; + self.want_grab = !open; + } } fn social(&mut self, open: bool) { - self.social = open; - self.spell = false; - self.want_grab = !open; + if !self.esc_menu { + self.social = open; + self.crafting = false; + self.spell = false; + self.want_grab = !open; + } + } + + fn crafting(&mut self, open: bool) { + if !self.esc_menu { + self.crafting = open; + self.bag = open; + self.want_grab = !open; + } } fn spell(&mut self, open: bool) { - self.social = false; - self.spell = open; - self.want_grab = !open; + if !self.esc_menu { + self.social = false; + self.crafting = false; + self.spell = open; + self.want_grab = !open; + } } fn toggle_map(&mut self) { self.map(!self.map) } @@ -383,15 +415,18 @@ impl Show { fn toggle_mini_map(&mut self) { self.mini_map = !self.mini_map; } fn settings(&mut self, open: bool) { - self.open_windows = if open { - Windows::Settings - } else { - Windows::None - }; - self.bag = false; - self.social = false; - self.spell = false; - self.want_grab = !open; + if !self.esc_menu { + self.open_windows = if open { + Windows::Settings + } else { + Windows::None + }; + self.bag = false; + self.social = false; + self.crafting = false; + self.spell = false; + self.want_grab = !open; + } } fn toggle_settings(&mut self) { @@ -410,6 +445,7 @@ impl Show { || self.esc_menu || self.map || self.social + || self.crafting || self.spell || self.help || self.intro @@ -425,21 +461,20 @@ impl Show { self.map = false; self.social = false; self.spell = false; + self.crafting = false; self.open_windows = Windows::None; self.want_grab = true; // Unpause the game if we are on singleplayer - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); } else { self.esc_menu = true; self.want_grab = false; // Pause the game if we are on singleplayer - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(true); - }; + #[cfg(feature = "singleplayer")] + global_state.pause(); } } @@ -456,6 +491,8 @@ impl Show { self.spell = false; } + fn toggle_crafting(&mut self) { self.crafting(!self.crafting) } + fn open_social_tab(&mut self, social_tab: SocialTab) { self.social_tab = social_tab; self.spell = false; @@ -555,6 +592,7 @@ impl Hud { esc_menu: false, open_windows: Windows::None, map: false, + crafting: false, ui: true, social: false, spell: false, @@ -598,6 +636,7 @@ impl Hud { debug_info: &Option, dt: Duration, info: HudInfo, + camera: &Camera, ) -> Vec { let mut events = std::mem::replace(&mut self.events, Vec::new()); let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets(); @@ -1475,6 +1514,7 @@ impl Hud { Some(buttons::Event::ToggleSocial) => self.show.toggle_social(), Some(buttons::Event::ToggleSpell) => self.show.toggle_spell(), Some(buttons::Event::ToggleMap) => self.show.toggle_map(), + Some(buttons::Event::ToggleCrafting) => self.show.toggle_crafting(), None => {}, } } @@ -1497,6 +1537,7 @@ impl Hud { &self.rot_imgs, &self.world_map, &self.fonts, + camera.get_orientation(), ) .set(self.ids.minimap, ui_widgets) { @@ -1531,7 +1572,6 @@ impl Hud { } } } - // Skillbar // Get player stats let ecs = client.state().ecs(); @@ -1579,6 +1619,35 @@ impl Hud { .set(self.ids.skillbar, ui_widgets); } + // Crafting + if self.show.crafting { + if let Some(inventory) = inventories.get(entity) { + for event in Crafting::new( + //&self.show, + client, + &self.imgs, + &self.fonts, + &self.voxygen_i18n, + &self.rot_imgs, + tooltip_manager, + &self.item_imgs, + &inventory, + ) + .set(self.ids.crafting_window, ui_widgets) + { + match event { + crafting::Event::CraftRecipe(r) => { + events.push(Event::CraftRecipe(r)); + }, + crafting::Event::Close => { + self.show.crafting(false); + self.force_ungrab = true; + }, + } + } + } + } + // Don't put NPC messages in chat box. self.new_messages .retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_, _))); @@ -1648,12 +1717,14 @@ impl Hud { }, settings_window::Event::ToggleHelp => self.show.help = !self.show.help, settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug, + settings_window::Event::ToggleTips(loading_tips) => { + events.push(Event::ToggleTips(loading_tips)); + }, settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab), settings_window::Event::Close => { // Unpause the game if we are on singleplayer so that we can logout - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); self.show.settings(false) }, @@ -1741,6 +1812,9 @@ impl Hud { settings_window::Event::ChangeBinding(game_input) => { events.push(Event::ChangeBinding(game_input)); }, + settings_window::Event::ResetBindings => { + events.push(Event::ResetBindings); + }, settings_window::Event::ChangeFreeLookBehavior(behavior) => { events.push(Event::ChangeFreeLookBehavior(behavior)); }, @@ -1832,24 +1906,21 @@ impl Hud { self.force_ungrab = false; // Unpause the game if we are on singleplayer - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); }, Some(esc_menu::Event::Logout) => { // Unpause the game if we are on singleplayer so that we can logout - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); events.push(Event::Logout); }, Some(esc_menu::Event::Quit) => events.push(Event::Quit), Some(esc_menu::Event::CharacterSelection) => { // Unpause the game if we are on singleplayer so that we can logout - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); events.push(Event::CharacterSelection) }, @@ -2061,6 +2132,10 @@ impl Hud { self.show.toggle_social(); true }, + GameInput::Crafting if state => { + self.show.toggle_crafting(); + true + }, GameInput::Spellbook if state => { self.show.toggle_spell(); true @@ -2243,7 +2318,7 @@ impl Hud { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } - let events = self.update_layout(client, global_state, debug_info, dt, info); + let events = self.update_layout(client, global_state, debug_info, dt, info, camera); let camera::Dependents { view_mat, proj_mat, .. } = camera.dependents(); diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 3e59ef93fb..a4b512e301 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -1,6 +1,6 @@ use super::{ - img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, XpBar, MENU_BG, - TEXT_COLOR, + img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, XpBar, + ERROR_COLOR, MENU_BG, TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, }; use crate::{ i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, @@ -33,6 +33,7 @@ widget_ids! { settings_scrollbar, controls_texts[], controls_buttons[], + reset_controls_button, controls_alignment_rectangle, button_help, button_help2, @@ -51,10 +52,10 @@ widget_ids! { languages_list, rectangle, general_txt, + load_tips_button, + load_tips_button_label, debug_button, debug_button_label, - tips_button, - tips_button_label, interface, language_text, mouse_pan_slider, @@ -225,6 +226,7 @@ pub struct State { pub enum Event { ToggleHelp, ToggleDebug, + ToggleTips(bool), ToggleXpBar(XpBar), ToggleBarNumbers(BarNumbers), ToggleShortcutNumbers(ShortcutNumbers), @@ -260,6 +262,7 @@ pub enum Event { SpeechBubbleIcon(bool), ChangeLanguage(LanguageMetadata), ChangeBinding(GameInput), + ResetBindings, ChangeFreeLookBehavior(PressBehavior), ChangeAutoWalkBehavior(PressBehavior), ChangeStopAutoWalkOnInput(bool), @@ -407,6 +410,31 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.show_help_label, ui); + // Loading Screen Tips + let show_tips = ToggleButton::new( + self.global_state.settings.gameplay.loading_tips, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .down_from(state.ids.button_help, 8.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.load_tips_button, ui); + + if self.global_state.settings.gameplay.loading_tips != show_tips { + events.push(Event::ToggleTips( + !self.global_state.settings.gameplay.loading_tips, + )); + } + + Text::new(&self.localized_strings.get("hud.settings.loading_tips")) + .right_from(state.ids.load_tips_button, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.load_tips_button) + .color(TEXT_COLOR) + .set(state.ids.load_tips_button_label, ui); // Debug let show_debug = ToggleButton::new( self.show.debug, @@ -414,7 +442,7 @@ impl<'a> Widget for SettingsWindow<'a> { self.imgs.checkbox_checked, ) .w_h(18.0, 18.0) - .down_from(state.ids.button_help, 8.0) + .down_from(state.ids.load_tips_button, 8.0) .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) .set(state.ids.debug_button, ui); @@ -1522,23 +1550,25 @@ impl<'a> Widget for SettingsWindow<'a> { // Contents if let SettingsTab::Controls = self.show.settings_tab { + // Used for sequential placement in a flow-down pattern + let mut previous_element_id = None; + let mut keybindings_vec: Vec = GameInput::iterator().collect(); + keybindings_vec.sort(); + let controls = &self.global_state.settings.controls; - if controls.keybindings.len() > state.ids.controls_texts.len() - || controls.keybindings.len() > state.ids.controls_buttons.len() + if keybindings_vec.len() > state.ids.controls_texts.len() + || keybindings_vec.len() > state.ids.controls_buttons.len() { state.update(|s| { s.ids .controls_texts - .resize(controls.keybindings.len(), &mut ui.widget_id_generator()); + .resize(keybindings_vec.len(), &mut ui.widget_id_generator()); s.ids .controls_buttons - .resize(controls.keybindings.len(), &mut ui.widget_id_generator()); + .resize(keybindings_vec.len(), &mut ui.widget_id_generator()); }); } - // Used for sequential placement in a flow-down pattern - let mut previous_text_id = None; - let mut keybindings_vec: Vec<&GameInput> = controls.keybindings.keys().collect(); - keybindings_vec.sort(); + // Loop all existing keybindings and the ids for text and button widgets for (game_input, (&text_id, &button_id)) in keybindings_vec.into_iter().zip( state @@ -1547,58 +1577,82 @@ impl<'a> Widget for SettingsWindow<'a> { .iter() .zip(state.ids.controls_buttons.iter()), ) { - if let Some(key) = controls.get_binding(*game_input) { - let loc_key = self - .localized_strings - .get(game_input.get_localization_key()); - let key_string = match self.global_state.window.remapping_keybindings { - Some(game_input_binding) => { - if *game_input == game_input_binding { - String::from(self.localized_strings.get("hud.settings.awaitingkey")) + let (key_string, key_color) = + if self.global_state.window.remapping_keybindings == Some(game_input) { + ( + String::from(self.localized_strings.get("hud.settings.awaitingkey")), + TEXT_COLOR, + ) + } else if let Some(key) = controls.get_binding(game_input) { + ( + key.to_string(), + if controls.has_conflicting_bindings(key) { + TEXT_BIND_CONFLICT_COLOR } else { - key.to_string() - } - }, - None => key.to_string(), + TEXT_COLOR + }, + ) + } else { + ( + String::from(self.localized_strings.get("hud.settings.unbound")), + ERROR_COLOR, + ) }; - - let text_widget = Text::new(loc_key) - .color(TEXT_COLOR) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(18)); - let button_widget = Button::new() - .label(&key_string) - .label_color(TEXT_COLOR) - .label_font_id(self.fonts.cyri.conrod_id) - .label_font_size(self.fonts.cyri.scale(15)) - .w(150.0) - .rgba(0.0, 0.0, 0.0, 0.0) - .border_rgba(0.0, 0.0, 0.0, 255.0) - .label_y(Relative::Scalar(3.0)); - // Place top-left if it's the first text, else under the previous one - let text_widget = match previous_text_id { - None => text_widget.top_left_with_margins_on( - state.ids.settings_content, - 10.0, - 5.0, - ), - Some(prev_id) => text_widget.down_from(prev_id, 10.0), - }; - let text_width = text_widget.get_w(ui).unwrap_or(0.0); - text_widget.set(text_id, ui); - if button_widget - .right_from(text_id, 350.0 - text_width) - .set(button_id, ui) - .was_clicked() - { - events.push(Event::ChangeBinding(*game_input)); - } - // Set the previous id to the current one for the next cycle - previous_text_id = Some(text_id); + let loc_key = self + .localized_strings + .get(game_input.get_localization_key()); + let text_widget = Text::new(loc_key) + .color(TEXT_COLOR) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)); + let button_widget = Button::new() + .label(&key_string) + .label_color(key_color) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(15)) + .w(150.0) + .rgba(0.0, 0.0, 0.0, 0.0) + .border_rgba(0.0, 0.0, 0.0, 255.0) + .label_y(Relative::Scalar(3.0)); + // Place top-left if it's the first text, else under the previous one + let text_widget = match previous_element_id { + None => { + text_widget.top_left_with_margins_on(state.ids.settings_content, 10.0, 5.0) + }, + Some(prev_id) => text_widget.down_from(prev_id, 10.0), + }; + let text_width = text_widget.get_w(ui).unwrap_or(0.0); + text_widget.set(text_id, ui); + if button_widget + .right_from(text_id, 350.0 - text_width) + .set(button_id, ui) + .was_clicked() + { + events.push(Event::ChangeBinding(game_input)); } + // Set the previous id to the current one for the next cycle + previous_element_id = Some(text_id); + } + if let Some(prev_id) = previous_element_id { + let key_string = self.localized_strings.get("hud.settings.reset_keybinds"); + let button_widget = Button::new() + .label(&key_string) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(18)) + .down_from(prev_id, 20.0) + .w(200.0) + .rgba(0.0, 0.0, 0.0, 0.0) + .border_rgba(0.0, 0.0, 0.0, 255.0) + .label_y(Relative::Scalar(3.0)) + .set(state.ids.reset_controls_button, ui); + if button_widget.was_clicked() { + events.push(Event::ResetBindings); + } + previous_element_id = Some(state.ids.reset_controls_button) } // Add an empty text widget to simulate some bottom margin, because conrod sucks - if let Some(prev_id) = previous_text_id { + if let Some(prev_id) = previous_element_id { Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT) .down_from(prev_id, 10.0) .set(state.ids.controls_alignment_rectangle, ui); diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 54a3a9e032..2e41c7a135 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -623,26 +623,7 @@ impl<'a> Widget for Skillbar<'a> { _ => self.imgs.nothing, }, ) // Insert Icon here - .w( - match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Bow(_) => 30.0 * scale, - ToolKind::Staff(_) => 32.0 * scale, - _ => 38.0 * scale, - }, - _ => 38.0 * scale, - }, - ) - .h( - match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Bow(_) => 30.0 * scale, - ToolKind::Staff(_) => 32.0 * scale, - _ => 38.0 * scale, - }, - _ => 38.0 * scale, - }, - ) + .w_h(32.0 * scale, 32.0 * scale) .middle_of(state.ids.m1_slot_bg) .set(state.ids.m1_content, ui); // M2 Slot @@ -704,7 +685,7 @@ impl<'a> Widget for Skillbar<'a> { .middle_of(state.ids.m2_slot) .set(state.ids.m2_slot_bg, ui); Button::image(match tool_kind { - Some(ToolKind::Sword(_)) => self.imgs.charge, + Some(ToolKind::Sword(_)) => self.imgs.twohsword_m2, Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2, Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2, Some(ToolKind::Hammer(_)) => self.imgs.hammerleap, @@ -714,17 +695,8 @@ impl<'a> Widget for Skillbar<'a> { Some(ToolKind::Staff(_)) => self.imgs.staff_m2, Some(ToolKind::Debug(DebugKind::Boost)) => self.imgs.flyingrod_m2, _ => self.imgs.nothing, - }) // Insert Icon here - .w(match tool_kind { - Some(ToolKind::Staff(_)) => 30.0 * scale, - Some(ToolKind::Bow(_)) => 30.0 * scale, - _ => 38.0 * scale, - }) - .h(match tool_kind { - Some(ToolKind::Staff(_)) => 30.0 * scale, - Some(ToolKind::Bow(_)) => 30.0 * scale, - _ => 38.0 * scale, }) + .w_h(32.0 * scale, 32.0 * scale) .middle_of(state.ids.m2_slot_bg) .image_color(match tool_kind { Some(ToolKind::Sword(_)) => { diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs new file mode 100644 index 0000000000..5b8c15cbb9 --- /dev/null +++ b/voxygen/src/hud/util.rs @@ -0,0 +1,85 @@ +use common::comp::item::{ + armor::{Armor, ArmorKind, Protection}, + Item, ItemKind, +}; +use std::borrow::Cow; + +pub fn loadout_slot_text<'a>( + item: Option<&'a Item>, + mut empty: impl FnMut() -> (&'a str, &'a str), +) -> (&'a str, Cow<'a, str>) { + item.map_or_else( + || { + let (title, desc) = empty(); + (title, Cow::Borrowed(desc)) + }, + item_text, + ) +} + +pub fn item_text<'a>(item: &'a Item) -> (&'_ str, Cow<'a, str>) { + let desc = match item.kind { + ItemKind::Armor(armor) => Cow::Owned(armor_desc(armor, item.description())), + // ItemKind::Tool => {}, + /*ItemKind::Consumable(kind, effect, ..) => { + Cow::Owned(consumable_desc(consumable, item.description())) + },*/ + // ItemKind::Throwable => {}, + // ItemKind::Utility => {}, + // ItemKind::Ingredient => {}, + // ItemKind::Lantern => {}, + _ => Cow::Borrowed(item.description()), + }; + + (item.name(), desc) +} +// Armor Description +fn armor_desc(armor: Armor, desc: &str) -> String { + // TODO: localization + let kind = match armor.kind { + ArmorKind::Shoulder(_) => "Shoulders", + ArmorKind::Chest(_) => "Chest", + ArmorKind::Belt(_) => "Belt", + ArmorKind::Hand(_) => "Hands", + ArmorKind::Pants(_) => "Legs", + ArmorKind::Foot(_) => "Feet", + ArmorKind::Back(_) => "Back", + ArmorKind::Ring(_) => "Ring", + ArmorKind::Neck(_) => "Neck", + ArmorKind::Head(_) => "Head", + ArmorKind::Tabard(_) => "Tabard", + }; + let armor = match armor.get_protection() { + Protection::Normal(a) => a.to_string(), + Protection::Invincible => "Inf".to_string(), + }; + + if !desc.is_empty() { + format!( + "{}\n\nArmor: {}\n\n{}\n\n", + kind, armor, desc + ) + } else { + format!("{}\n\nArmor: {}\n\n", kind, armor) + } +} +// Weapon/Tool Description + +// Consumable Description +/*fn consumable_desc(consumable: Consumable, desc: &str) -> String { + // TODO: localization + let kind = "Consumable"; + if !desc.is_empty() { + format!("{}\n\n{}\n\n", kind, desc) + } else { + format!("{}\n\n", kind) + } +}*/ + +// Throwable Description + +// Utility Description + +// Ingredient Description + +// Lantern Description diff --git a/voxygen/src/i18n.rs b/voxygen/src/i18n.rs index 57b6a376e7..9858ff74c0 100644 --- a/voxygen/src/i18n.rs +++ b/voxygen/src/i18n.rs @@ -175,3 +175,385 @@ pub fn list_localizations() -> Vec { /// Return the asset associated with the language_id pub fn i18n_asset_key(language_id: &str) -> String { "voxygen.i18n.".to_string() + language_id } + +#[cfg(test)] +mod tests { + use super::VoxygenLocalization; + use git2::Repository; + use ron::de::from_bytes; + use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, + }; + + /// List localization files as a PathBuf vector + fn i18n_files(i18n_dir: &Path) -> Vec { + fs::read_dir(i18n_dir) + .unwrap() + .map(|res| res.map(|e| e.path()).unwrap()) + .filter(|e| match e.extension() { + Some(ext) => ext == "ron", + None => false, + }) + .collect() + } + + #[derive(Debug, PartialEq)] + enum LocalizationState { + UpToDate, + NotFound, + Outdated, + Unknown, + Unused, + } + + #[derive(Debug)] + struct LocalizationEntryState { + pub key_line: Option, + pub chuck_line_range: Option<(usize, usize)>, + pub commit_id: Option, + pub state: LocalizationState, + } + + impl LocalizationEntryState { + pub fn new() -> LocalizationEntryState { + LocalizationEntryState { + key_line: None, + chuck_line_range: None, + commit_id: None, + state: LocalizationState::Unknown, + } + } + } + + /// Returns the Git blob associated with the given reference and path + #[allow(clippy::expect_fun_call)] // TODO: Pending review in #587 + fn read_file_from_path<'a>( + repo: &'a git2::Repository, + reference: &git2::Reference, + path: &std::path::Path, + ) -> git2::Blob<'a> { + let tree = reference + .peel_to_tree() + .expect("Impossible to peel HEAD to a tree object"); + tree.get_path(path) + .expect(&format!( + "Impossible to find the file {:?} in reference {:?}", + path, + reference.name() + )) + .to_object(&repo) + .unwrap() + .peel_to_blob() + .expect("Impossible to fetch the Git object") + } + + fn generate_key_version<'a>( + repo: &'a git2::Repository, + localization: &VoxygenLocalization, + path: &std::path::Path, + file_blob: &git2::Blob, + ) -> HashMap { + let mut keys: HashMap = localization + .string_map + .keys() + .map(|k| (k.to_owned(), LocalizationEntryState::new())) + .collect(); + let mut to_process: HashSet<&String> = localization.string_map.keys().collect(); + // Find key start lines + let file_content = std::str::from_utf8(file_blob.content()).expect("Got non UTF-8 file"); + for (line_nb, line) in file_content.lines().enumerate() { + let mut found_key = None; + + for key in to_process.iter() { + if line.contains(key.as_str()) { + found_key = Some(key.to_owned()); + break; + } + } + + if let Some(key) = found_key { + keys.get_mut(key).unwrap().key_line = Some(line_nb); + to_process.remove(&key); + }; + } + + let mut error_check_set: Vec = vec![]; + // Find commit for each keys + repo.blame_file(path, None) + .expect("Impossible to generate the Git blame") + .iter() + .for_each(|e: git2::BlameHunk| { + for (key, state) in keys.iter_mut() { + let line = match state.key_line { + Some(l) => l, + None => { + if !error_check_set.contains(key) { + eprintln!( + "Key {} does not have a git line in it's state! Skipping key.", + key + ); + error_check_set.push(key.clone()); + } + continue; + }, + }; + + if line >= e.final_start_line() + && line < e.final_start_line() + e.lines_in_hunk() + { + state.chuck_line_range = Some(( + e.final_start_line(), + e.final_start_line() + e.lines_in_hunk(), + )); + state.commit_id = match state.commit_id { + Some(existing_commit) => { + match repo.graph_descendant_of(e.final_commit_id(), existing_commit) + { + Ok(true) => Some(e.final_commit_id()), + Ok(false) => Some(existing_commit), + Err(err) => panic!(err), + } + }, + None => Some(e.final_commit_id()), + }; + } + } + }); + + keys + } + + #[test] + #[ignore] + #[allow(clippy::expect_fun_call)] + /// Test to verify all languages and print missing and faulty localisation + fn test_all_localizations() { + // Generate paths + let i18n_asset_path = Path::new("assets/voxygen/i18n/"); + let en_i18n_path = i18n_asset_path.join("en.ron"); + let root_dir = std::env::current_dir() + .map(|p| p.parent().expect("").to_owned()) + .unwrap(); + let i18n_path = root_dir.join(i18n_asset_path); + + if !root_dir.join(&en_i18n_path).is_file() { + panic!("Reference language file not found {:?}", &en_i18n_path) + } + + // Initialize Git objects + let repo = Repository::discover(&root_dir).expect(&format!( + "Failed to open the Git repository at {:?}", + &root_dir + )); + let head_ref = repo.head().expect("Impossible to get the HEAD reference"); + + // Read HEAD for the reference language file + let i18n_en_blob = read_file_from_path(&repo, &head_ref, &en_i18n_path); + let loc: VoxygenLocalization = from_bytes(i18n_en_blob.content()) + .expect("Expect to parse reference i18n RON file, can't proceed without it"); + let i18n_references: HashMap = + generate_key_version(&repo, &loc, &en_i18n_path, &i18n_en_blob); + + // Compare to other reference files + let i18n_files = i18n_files(&i18n_path); + let mut i18n_entry_counts: HashMap = HashMap::new(); + for file in &i18n_files { + let relfile = file.strip_prefix(&root_dir).unwrap(); + if relfile == en_i18n_path { + continue; + } + println!("\n-----------------------------------"); + println!("{:?}", relfile); + println!("-----------------------------------"); + + // Find the localization entry state + let current_blob = read_file_from_path(&repo, &head_ref, &relfile); + let current_loc: VoxygenLocalization = match from_bytes(current_blob.content()) { + Ok(v) => v, + Err(e) => { + eprintln!( + "Could not parse {} RON file, skipping: {}", + relfile.to_string_lossy(), + e + ); + continue; + }, + }; + let mut current_i18n = + generate_key_version(&repo, ¤t_loc, &relfile, ¤t_blob); + for (ref_key, ref_state) in i18n_references.iter() { + match current_i18n.get_mut(ref_key) { + Some(state) => { + let commit_id = match state.commit_id { + Some(c) => c, + None => { + eprintln!( + "Commit ID of key {} in i18n file {} is missing! Skipping key.", + ref_key, + relfile.to_string_lossy() + ); + continue; + }, + }; + let ref_commit_id = match ref_state.commit_id { + Some(c) => c, + None => { + eprintln!( + "Commit ID of key {} in reference i18n file is missing! \ + Skipping key.", + ref_key + ); + continue; + }, + }; + if commit_id != ref_commit_id + && !repo + .graph_descendant_of(commit_id, ref_commit_id) + .unwrap_or(false) + { + state.state = LocalizationState::Outdated; + } else { + state.state = LocalizationState::UpToDate; + } + }, + None => { + current_i18n.insert(ref_key.to_owned(), LocalizationEntryState { + key_line: None, + chuck_line_range: None, + commit_id: None, + state: LocalizationState::NotFound, + }); + }, + } + } + + let ref_keys: HashSet<&String> = i18n_references.keys().collect(); + for (_, state) in current_i18n + .iter_mut() + .filter(|&(k, _)| !ref_keys.contains(k)) + { + state.state = LocalizationState::Unused; + } + + // Display + println!( + "\n{:10} | {:60}| {:40} | {:40}\n", + "State", + "Key name", + relfile.to_str().unwrap(), + en_i18n_path.to_str().unwrap() + ); + + let mut sorted_keys: Vec<&String> = current_i18n.keys().collect(); + sorted_keys.sort(); + + let current_i18n_entry_count = current_i18n.len(); + let mut uptodate_entries = 0; + let mut outdated_entries = 0; + let mut unused_entries = 0; + let mut notfound_entries = 0; + let mut unknown_entries = 0; + + for key in sorted_keys { + let state = current_i18n.get(key).unwrap(); + if state.state != LocalizationState::UpToDate { + match state.state { + LocalizationState::Outdated => outdated_entries += 1, + LocalizationState::NotFound => notfound_entries += 1, + LocalizationState::Unknown => unknown_entries += 1, + LocalizationState::Unused => unused_entries += 1, + LocalizationState::UpToDate => unreachable!(), + }; + + println!( + "[{:9}] | {:60}| {:40} | {:40}", + format!("{:?}", state.state), + key, + state + .commit_id + .map(|s| format!("{}", s)) + .unwrap_or_else(|| "None".to_string()), + i18n_references + .get(key) + .map(|s| s.commit_id) + .flatten() + .map(|s| format!("{}", s)) + .unwrap_or_else(|| "None".to_string()), + ); + } else { + uptodate_entries += 1; + } + } + + println!( + "\n{} up-to-date, {} outdated, {} unused, {} not found, {} unknown entries", + uptodate_entries, + outdated_entries, + unused_entries, + notfound_entries, + unknown_entries + ); + + // Calculate key count that actually matter for the status of the translation + // Unused entries don't break the game + let real_entry_count = current_i18n_entry_count - unused_entries; + let uptodate_percent = (uptodate_entries as f32 / real_entry_count as f32) * 100_f32; + let outdated_percent = (outdated_entries as f32 / real_entry_count as f32) * 100_f32; + let untranslated_percent = + ((notfound_entries + unknown_entries) as f32 / real_entry_count as f32) * 100_f32; + + println!( + "{:.2}% up-to-date, {:.2}% outdated, {:.2}% untranslated\n", + uptodate_percent, outdated_percent, untranslated_percent, + ); + + i18n_entry_counts.insert( + file.clone(), + ( + uptodate_entries, + outdated_entries, + notfound_entries + unknown_entries, + real_entry_count, + ), + ); + } + + let mut overall_uptodate_entry_count = 0; + let mut overall_outdated_entry_count = 0; + let mut overall_untranslated_entry_count = 0; + let mut overall_real_entry_count = 0; + + println!("-----------------------------------------------------------------------------"); + println!("Overall Translation Status"); + println!("-----------------------------------------------------------------------------"); + println!( + "{:12}| {:8} | {:8} | {:8}", + "", "up-to-date", "outdated", "untranslated" + ); + + for (path, (uptodate, outdated, untranslated, real)) in i18n_entry_counts { + overall_uptodate_entry_count += uptodate; + overall_outdated_entry_count += outdated; + overall_untranslated_entry_count += untranslated; + overall_real_entry_count += real; + + println!( + "{:12}|{:8} |{:6} |{:8}", + path.file_name().unwrap().to_string_lossy(), + uptodate, + outdated, + untranslated + ); + } + + println!( + "\n{:.2}% up-to-date, {:.2}% outdated, {:.2}% untranslated", + (overall_uptodate_entry_count as f32 / overall_real_entry_count as f32) * 100_f32, + (overall_outdated_entry_count as f32 / overall_real_entry_count as f32) * 100_f32, + (overall_untranslated_entry_count as f32 / overall_real_entry_count as f32) * 100_f32, + ); + println!("-----------------------------------------------------------------------------\n"); + } +} diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 02bc36d053..ccec393b41 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -17,6 +17,7 @@ pub mod menu; pub mod mesh; pub mod profile; pub mod render; +pub mod run; pub mod scene; pub mod session; pub mod settings; @@ -27,10 +28,16 @@ pub mod window; // Reexports pub use crate::error::Error; +#[cfg(feature = "singleplayer")] +use crate::singleplayer::Singleplayer; use crate::{ - audio::AudioFrontend, profile::Profile, settings::Settings, singleplayer::Singleplayer, - window::Window, + audio::AudioFrontend, + profile::Profile, + render::Renderer, + settings::Settings, + window::{Event, Window}, }; +use common::{assets::watch, clock::Clock}; /// A type used to store state that is shared between all play states. pub struct GlobalState { @@ -39,7 +46,11 @@ pub struct GlobalState { pub window: Window, pub audio: AudioFrontend, pub info_message: Option, + pub clock: Clock, + #[cfg(feature = "singleplayer")] pub singleplayer: Option, + // TODO: redo this so that the watcher doesn't have to exist for reloading to occur + pub localization_watcher: watch::ReloadIndicator, } impl GlobalState { @@ -51,8 +62,25 @@ impl GlobalState { } pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); } + + #[cfg(feature = "singleplayer")] + pub fn paused(&self) -> bool { + self.singleplayer + .as_ref() + .map_or(false, Singleplayer::is_paused) + } + + #[cfg(not(feature = "singleplayer"))] + pub fn paused(&self) -> bool { false } + + #[cfg(feature = "singleplayer")] + pub fn unpause(&self) { self.singleplayer.as_ref().map(|s| s.pause(false)); } + + #[cfg(feature = "singleplayer")] + pub fn pause(&self) { self.singleplayer.as_ref().map(|s| s.pause(true)); } } +// TODO: appears to be currently unused by playstates pub enum Direction { Forwards, Backwards, @@ -61,6 +89,8 @@ pub enum Direction { /// States can either close (and revert to a previous state), push a new state /// on top of themselves, or switch to a totally different state. pub enum PlayStateResult { + /// Keep running this play state. + Continue, /// Pop all play states in reverse order and shut down the program. Shutdown, /// Close the current play state and pop it from the play state stack. @@ -74,10 +104,15 @@ pub enum PlayStateResult { /// A trait representing a playable game state. This may be a menu, a game /// session, the title screen, etc. pub trait PlayState { - /// Play the state until some change of state is required (i.e: a menu is - /// opened or the game is closed). - fn play(&mut self, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult; + /// Called when entering this play state from another + fn enter(&mut self, global_state: &mut GlobalState, direction: Direction); + + /// Tick the play state + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult; /// Get a descriptive name for this state type. fn name(&self) -> &'static str; + + /// Draw the play state. + fn render(&mut self, renderer: &mut Renderer, settings: &Settings); } diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index b4f64dc017..d119fdb788 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -7,76 +7,33 @@ use veloren_voxygen::{ audio::{self, AudioFrontend}, i18n::{self, i18n_asset_key, VoxygenLocalization}, logging, - menu::main::MainMenuState, profile::Profile, + run, settings::{AudioOutput, Settings}, window::Window, - Direction, GlobalState, PlayState, PlayStateResult, + GlobalState, }; -use common::assets::{load, load_expect}; -use std::{mem, panic}; -use tracing::{debug, error, warn}; +use common::{ + assets::{load_watched, watch}, + clock::Clock, +}; +use std::panic; +use tracing::{error, warn}; fn main() { - #[cfg(feature = "tweak")] - const_tweaker::run().expect("Could not run server"); - // Load the settings // Note: This won't log anything due to it being called before // `logging::init`. The issue is we need to read a setting to decide // whether we create a log file or not. - let settings = Settings::load(); - - // Init logging and hold the guards. - let _guards = logging::init(&settings); - - // Save settings to add new fields or create the file if it is not already - // there. + let mut settings = Settings::load(); + // Save settings to add new fields or create the file if it is not already there if let Err(err) = settings.save_to_file() { panic!("Failed to save settings: {:?}", err); } - 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); - - audio.set_music_volume(settings.audio.music_volume); - audio.set_sfx_volume(settings.audio.sfx_volume); - - // Load the profile. - let profile = Profile::load(); - - let mut global_state = GlobalState { - audio, - profile, - window: Window::new(&settings).expect("Failed to create window!"), - settings, - info_message: None, - singleplayer: None, - }; - - // Try to load the localization and log missing entries - let localized_strings = load::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )) - .unwrap_or_else(|e| { - let preferred_language = &global_state.settings.language.selected_language; - warn!( - ?e, - ?preferred_language, - "Impossible to load language: change to the default language (English) instead.", - ); - global_state.settings.language.selected_language = i18n::REFERENCE_LANG.to_owned(); - load_expect::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )) - }); - localized_strings.log_missing_entries(); + // Init logging and hold the guards. + let _guards = logging::init(&settings); // Set up panic handler to relay swish panic messages to the user let default_hook = panic::take_hook(); @@ -159,68 +116,56 @@ fn main() { #[cfg(feature = "hot-anim")] anim::init(); - // Set up the initial play state. - let mut states: Vec> = vec![Box::new(MainMenuState::new(&mut global_state))]; - states.last().map(|current_state| { - let current_state = current_state.name(); - debug!(?current_state, "Started game with state") - }); - - // What's going on here? - // --------------------- - // The state system used by Voxygen allows for the easy development of - // stack-based menus. For example, you may want a "title" state that can - // push a "main menu" state on top of it, which can in turn push a - // "settings" state or a "game session" state on top of it. The code below - // manages the state transfer logic automatically so that we don't have to - // re-engineer it for each menu we decide to add to the game. - let mut direction = Direction::Forwards; - while let Some(state_result) = states - .last_mut() - .map(|last| last.play(direction, &mut global_state)) - { - // Implement state transfer logic. - match state_result { - PlayStateResult::Shutdown => { - direction = Direction::Backwards; - debug!("Shutting down all states..."); - while states.last().is_some() { - states.pop().map(|old_state| { - let old_state = old_state.name(); - debug!(?old_state, "Popped state"); - global_state.on_play_state_changed(); - }); - } - }, - PlayStateResult::Pop => { - direction = Direction::Backwards; - states.pop().map(|old_state| { - let old_state = old_state.name(); - debug!(?old_state, "Popped state"); - global_state.on_play_state_changed(); - }); - }, - PlayStateResult::Push(new_state) => { - direction = Direction::Forwards; - debug!("Pushed state '{}'.", new_state.name()); - states.push(new_state); - global_state.on_play_state_changed(); - }, - PlayStateResult::Switch(mut new_state_box) => { - direction = Direction::Forwards; - states.last_mut().map(|old_state_box| { - let old_state = old_state_box.name(); - let new_state = new_state_box.name(); - debug!(?old_state, ?new_state, "Switching to states",); - mem::swap(old_state_box, &mut new_state_box); - global_state.on_play_state_changed(); - }); - }, - } + // 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); - // Save any unsaved changes to profile. - global_state.profile.save_to_file_warn(); - // Save any unsaved changes to settings. - global_state.settings.save_to_file_warn(); + audio.set_music_volume(settings.audio.music_volume); + audio.set_sfx_volume(settings.audio.sfx_volume); + + // Load the profile. + let profile = Profile::load(); + + let mut localization_watcher = watch::ReloadIndicator::new(); + let localized_strings = load_watched::( + &i18n_asset_key(&settings.language.selected_language), + &mut localization_watcher, + ) + .unwrap_or_else(|error| { + let selected_language = &settings.language.selected_language; + warn!( + ?error, + ?selected_language, + "Impossible to load language: change to the default language (English) instead.", + ); + settings.language.selected_language = i18n::REFERENCE_LANG.to_owned(); + load_watched::( + &i18n_asset_key(&settings.language.selected_language), + &mut localization_watcher, + ) + .unwrap() + }); + localized_strings.log_missing_entries(); + + // Create window + let (window, event_loop) = Window::new(&settings).expect("Failed to create window!"); + + let global_state = GlobalState { + audio, + profile, + window, + settings, + clock: Clock::start(), + info_message: None, + #[cfg(feature = "singleplayer")] + singleplayer: None, + localization_watcher, + }; + + run::run(global_state, event_loop); } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 442a0c034b..59d88b56ed 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -2,15 +2,17 @@ mod ui; use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, + render::Renderer, scene::simple::{self as scene, Scene}, session::SessionState, + settings::Settings, window::Event as WinEvent, Direction, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{assets, clock::Clock, comp, msg::ClientState, state::DeltaTime}; +use common::{assets, comp, msg::ClientState, state::DeltaTime}; use specs::WorldExt; -use std::{cell::RefCell, rc::Rc, time::Duration}; +use std::{cell::RefCell, rc::Rc}; use tracing::error; use ui::CharSelectionUi; @@ -33,20 +35,34 @@ impl CharSelectionState { scene, } } + + fn get_humanoid_body(&self) -> Option { + self.char_selection_ui + .get_character_list() + .and_then(|data| { + if let Some(character) = data.get(self.char_selection_ui.selected_character) { + match character.body { + comp::Body::Humanoid(body) => Some(body), + _ => None, + } + } else { + None + } + }) + } } impl PlayState for CharSelectionState { - fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult { - // Set up an fps clock. - let mut clock = Clock::start(); - + fn enter(&mut self, _: &mut GlobalState, _: Direction) { // Load the player's character list self.client.borrow_mut().load_character_list(); + } - let mut current_client_state = self.client.borrow().get_client_state(); - while let ClientState::Pending | ClientState::Registered = current_client_state { + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { + let client_state = self.client.borrow().get_client_state(); + if let ClientState::Pending | ClientState::Registered = client_state { // Handle window events - for event in global_state.window.fetch_events(&mut global_state.settings) { + for event in events { if self.char_selection_ui.handle_event(event.clone()) { continue; } @@ -61,10 +77,6 @@ impl PlayState for CharSelectionState { } } - let renderer = global_state.window.renderer_mut(); - renderer.clear(); - renderer.clear_shadows(); - // Maintain the UI. let events = self .char_selection_ui @@ -103,23 +115,7 @@ impl PlayState for CharSelectionState { } } - // Maintain global state. - global_state.maintain(clock.get_last_delta().as_secs_f32()); - - let humanoid_body = self - .char_selection_ui - .get_character_list() - .and_then(|data| { - if let Some(character) = data.get(self.char_selection_ui.selected_character) { - match character.body { - comp::Body::Humanoid(body) => Some(body), - _ => None, - } - } else { - None - } - }); - + let humanoid_body = self.get_humanoid_body(); let loadout = self.char_selection_ui.get_loadout(); // Maintain the scene. @@ -144,17 +140,6 @@ impl PlayState for CharSelectionState { loadout.as_ref(), ); } - // Render the scene. - self.scene.render( - global_state.window.renderer_mut(), - self.client.borrow().get_tick(), - humanoid_body, - loadout.as_ref(), - ); - - // Draw the UI to the screen. - self.char_selection_ui - .render(global_state.window.renderer_mut(), self.scene.globals()); // Tick the client (currently only to keep the connection alive). let localized_strings = assets::load_expect::(&i18n_asset_key( @@ -163,7 +148,7 @@ impl PlayState for CharSelectionState { match self.client.borrow_mut().tick( comp::ControllerInputs::default(), - clock.get_last_delta(), + global_state.clock.get_last_delta(), |_| {}, ) { Ok(events) => { @@ -193,25 +178,33 @@ impl PlayState for CharSelectionState { }, } + // TODO: make sure rendering is not relying on cleaned up stuff self.client.borrow_mut().cleanup(); - // Finish the frame. - global_state.window.renderer_mut().flush(); - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers"); - - // Wait for the next tick. - clock.tick(Duration::from_millis( - 1000 / (global_state.settings.graphics.max_fps as u64), - )); - - current_client_state = self.client.borrow().get_client_state(); + PlayStateResult::Continue + } else { + error!("Client not in pending or registered state. Popping char selection play state"); + // TODO set global_state.info_message + PlayStateResult::Pop } - - PlayStateResult::Pop } fn name(&self) -> &'static str { "Title" } + + fn render(&mut self, renderer: &mut Renderer, _: &Settings) { + let humanoid_body = self.get_humanoid_body(); + let loadout = self.char_selection_ui.get_loadout(); + + // Render the scene. + self.scene.render( + renderer, + self.client.borrow().get_tick(), + humanoid_body, + loadout.as_ref(), + ); + + // Draw the UI to the screen. + self.char_selection_ui + .render(renderer, self.scene.globals()); + } } diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 01a8d47cd0..9ae1c3288d 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -182,9 +182,6 @@ image_ids! { charlist_frame: "voxygen.element.frames.window_4", server_frame: "voxygen.element.frames.server_frame", - selection: "voxygen.element.frames.selection", - selection_hover: "voxygen.element.frames.selection_hover", - selection_press: "voxygen.element.frames.selection_press", // Info Window info_frame: "voxygen.element.frames.info_frame", @@ -195,6 +192,9 @@ image_ids! { delete_button_press: "voxygen.element.buttons.x_red_press", + selection: "voxygen.element.frames.selection", + selection_hover: "voxygen.element.frames.selection_hover", + selection_press: "voxygen.element.frames.selection_press", name_input: "voxygen.element.misc_bg.textbox_mid", @@ -241,8 +241,7 @@ image_ids! { } rotation_image_ids! { pub struct ImgsRot { - - + // Tooltip Test tt_side: "voxygen/element/frames/tt_test_edge", tt_corner: "voxygen/element/frames/tt_test_corner_tr", diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index c11d848c83..f3ec5fdb2d 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -12,7 +12,7 @@ use std::{ thread, time::Duration, }; -use tracing::debug; +use tracing::{debug, trace, warn}; #[derive(Debug)] pub enum Error { @@ -81,7 +81,7 @@ impl ClientInit { { match Client::new(socket_addr, view_distance) { Ok(mut client) => { - if let Err(err) = + if let Err(e) = client.register(username, password, |auth_server| { let _ = tx .send(Msg::IsAuthTrusted(auth_server.to_string())); @@ -93,29 +93,28 @@ impl ClientInit { .unwrap_or(false) }) { - last_err = Some(Error::ClientError(err)); + last_err = Some(Error::ClientError(e)); break 'tries; } let _ = tx.send(Msg::Done(Ok(client))); return; }, - Err(err) => { - match err { - ClientError::NetworkErr(NetworkError::ConnectFailed( - .., - )) => { - debug!( - "can't reach the server, going to retry in a few \ - seconds" - ); - }, - // Non-connection error, stop attempts - err => { - last_err = Some(Error::ClientError(err)); - break 'tries; - }, + Err(ClientError::NetworkErr(NetworkError::ConnectFailed(e))) => { + if e.kind() == std::io::ErrorKind::PermissionDenied { + warn!(?e, "Cannot connect to server: Incompatible version"); + last_err = Some(Error::ClientError( + ClientError::NetworkErr(NetworkError::ConnectFailed(e)), + )); + break 'tries; + } else { + debug!("Cannot connect to server: Timeout (retrying...)"); } }, + Err(e) => { + trace!(?e, "Aborting server connection attempt"); + last_err = Some(Error::ClientError(e)); + break 'tries; + }, } } thread::sleep(Duration::from_secs(5)); diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index cdd9410545..c73883218d 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -1,19 +1,22 @@ mod client_init; -#[cfg(feature = "singleplayer")] mod ui; +mod ui; use super::char_selection::CharSelectionState; +#[cfg(feature = "singleplayer")] +use crate::singleplayer::Singleplayer; use crate::{ - singleplayer::Singleplayer, window::Event, Direction, GlobalState, PlayState, PlayStateResult, + render::Renderer, settings::Settings, window::Event, Direction, GlobalState, PlayState, + PlayStateResult, }; use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; -use common::{assets::load_expect, clock::Clock, comp}; -#[cfg(feature = "singleplayer")] -use std::time::Duration; +use common::{assets::load_expect, comp}; use tracing::{error, warn}; use ui::{Event as MainMenuEvent, MainMenuUi}; pub struct MainMenuState { main_menu_ui: MainMenuUi, + // Used for client creation. + client_init: Option, } impl MainMenuState { @@ -21,6 +24,7 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), + client_init: None, } } } @@ -28,237 +32,242 @@ impl MainMenuState { const DEFAULT_PORT: u16 = 14004; impl PlayState for MainMenuState { - #[allow(clippy::useless_format)] // TODO: Pending review in #587 - fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult { - // Set up an fps clock. - let mut clock = Clock::start(); - - // Used for client creation. - let mut client_init: Option = None; - + fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { // Kick off title music if global_state.settings.audio.output.is_enabled() && global_state.audio.music_enabled() { global_state.audio.play_title_music(); } // Reset singleplayer server if it was running already - global_state.singleplayer = None; + #[cfg(feature = "singleplayer")] + { + global_state.singleplayer = None; + } + } + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { let localized_strings = load_expect::( &crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language), ); - loop { - // Handle window events. - for event in global_state.window.fetch_events(&mut global_state.settings) { - match event { - Event::Close => return PlayStateResult::Shutdown, - // Pass events to ui. - Event::Ui(event) => { - self.main_menu_ui.handle_event(event); - }, - // Ignore all other events. - _ => {}, + //Poll server creation + #[cfg(feature = "singleplayer")] + { + if let Some(singleplayer) = &global_state.singleplayer { + if let Ok(result) = singleplayer.receiver.try_recv() { + if let Err(error) = result { + tracing::error!(?error, "Could not start server"); + global_state.singleplayer = None; + self.client_init = None; + self.main_menu_ui.cancel_connection(); + self.main_menu_ui.show_info(format!("Error: {:?}", error)); + } } } + } - global_state.window.renderer_mut().clear(); - - // Poll client creation. - match client_init.as_ref().and_then(|init| init.poll()) { - Some(InitMsg::Done(Ok(mut client))) => { - self.main_menu_ui.connected(); - // Register voxygen components / resources - crate::ecs::init(client.state_mut().ecs_mut()); - return PlayStateResult::Push(Box::new(CharSelectionState::new( - global_state, - std::rc::Rc::new(std::cell::RefCell::new(client)), - ))); + // Handle window events. + for event in events { + match event { + Event::Close => return PlayStateResult::Shutdown, + // Pass events to ui. + Event::Ui(event) => { + self.main_menu_ui.handle_event(event); }, - Some(InitMsg::Done(Err(e))) => { - client_init = None; - global_state.info_message = Some({ - let err = match e { - InitError::BadAddress(_) | InitError::NoAddress => { - localized_strings.get("main.login.server_not_found").into() - }, - InitError::ClientError(err) => match err { - client::Error::AuthErr(e) => format!( - "{}: {}", - localized_strings.get("main.login.authentication_error"), - e - ), - client::Error::TooManyPlayers => { - localized_strings.get("main.login.server_full").into() - }, - client::Error::AuthServerNotTrusted => localized_strings - .get("main.login.untrusted_auth_server") - .into(), - client::Error::ServerWentMad => localized_strings - .get("main.login.outdated_client_or_server") - .into(), - client::Error::ServerTimeout => { - localized_strings.get("main.login.timeout").into() - }, - client::Error::ServerShutdown => { - localized_strings.get("main.login.server_shut_down").into() - }, - client::Error::AlreadyLoggedIn => { - localized_strings.get("main.login.already_logged_in").into() - }, - client::Error::NotOnWhitelist => { - localized_strings.get("main.login.not_on_whitelist").into() - }, - client::Error::NetworkErr(e) => format!( - "{}: {:?}", - localized_strings.get("main.login.network_error"), - e - ), - client::Error::ParticipantErr(e) => format!( - "{}: {:?}", - localized_strings.get("main.login.network_error"), - e - ), - client::Error::StreamErr(e) => format!( - "{}: {:?}", - localized_strings.get("main.login.network_error"), - e - ), - client::Error::Other(e) => { - format!("{}: {}", localized_strings.get("common.error"), e) - }, - client::Error::AuthClientError(e) => match e { - client::AuthClientError::JsonError(e) => format!( - "{}: {}", - localized_strings.get("common.fatal_error"), - e - ), - client::AuthClientError::RequestError() => format!( - "{}", - localized_strings.get("main.login.failed_sending_request") - ), - client::AuthClientError::ServerError(_, e) => format!("{}", e), - }, - client::Error::InvalidCharacter => { - localized_strings.get("main.login.invalid_character").into() - }, - }, - InitError::ClientCrashed => { - localized_strings.get("main.login.client_crashed").into() - }, - }; - // Log error for possible additional use later or incase that the error - // displayed is cut of. - error!("{}", err); - err - }); - }, - Some(InitMsg::IsAuthTrusted(auth_server)) => { - if global_state - .settings - .networking - .trusted_auth_servers - .contains(&auth_server) - { - // Can't fail since we just polled it, it must be Some - client_init.as_ref().unwrap().auth_trust(auth_server, true); - } else { - // Show warning that auth server is not trusted and prompt for approval - self.main_menu_ui.auth_trust_prompt(auth_server); - } - }, - None => {}, + // Ignore all other events. + _ => {}, } + } + // Poll client creation. + match self.client_init.as_ref().and_then(|init| init.poll()) { + Some(InitMsg::Done(Ok(mut client))) => { + self.client_init = None; + self.main_menu_ui.connected(); + // Register voxygen components / resources + crate::ecs::init(client.state_mut().ecs_mut()); + return PlayStateResult::Push(Box::new(CharSelectionState::new( + global_state, + std::rc::Rc::new(std::cell::RefCell::new(client)), + ))); + }, + Some(InitMsg::Done(Err(err))) => { + self.client_init = None; + global_state.info_message = Some({ + let err = match err { + InitError::BadAddress(_) | InitError::NoAddress => { + localized_strings.get("main.login.server_not_found").into() + }, + InitError::ClientError(err) => match err { + client::Error::AuthErr(e) => format!( + "{}: {}", + localized_strings.get("main.login.authentication_error"), + e + ), + client::Error::TooManyPlayers => { + localized_strings.get("main.login.server_full").into() + }, + client::Error::AuthServerNotTrusted => localized_strings + .get("main.login.untrusted_auth_server") + .into(), + client::Error::ServerWentMad => localized_strings + .get("main.login.outdated_client_or_server") + .into(), + client::Error::ServerTimeout => { + localized_strings.get("main.login.timeout").into() + }, + client::Error::ServerShutdown => { + localized_strings.get("main.login.server_shut_down").into() + }, + client::Error::AlreadyLoggedIn => { + localized_strings.get("main.login.already_logged_in").into() + }, + client::Error::NotOnWhitelist => { + localized_strings.get("main.login.not_on_whitelist").into() + }, + client::Error::InvalidCharacter => { + localized_strings.get("main.login.invalid_character").into() + }, + client::Error::NetworkErr(e) => format!( + "{}: {:?}", + localized_strings.get("main.login.network_error"), + e + ), + client::Error::ParticipantErr(e) => format!( + "{}: {:?}", + localized_strings.get("main.login.network_error"), + e + ), + client::Error::StreamErr(e) => format!( + "{}: {:?}", + localized_strings.get("main.login.network_error"), + e + ), + client::Error::Other(e) => { + format!("{}: {}", localized_strings.get("common.error"), e) + }, + client::Error::AuthClientError(e) => match e { + client::AuthClientError::JsonError(e) => format!( + "{}: {}", + localized_strings.get("common.fatal_error"), + e + ), + // TODO: remove parentheses + client::AuthClientError::RequestError() => localized_strings + .get("main.login.failed_sending_request") + .to_owned(), + client::AuthClientError::ServerError(_, e) => e, + }, + }, + InitError::ClientCrashed => { + localized_strings.get("main.login.client_crashed").into() + }, + }; + // Log error for possible additional use later or incase that the error + // displayed is cut of. + error!("{}", err); + err + }); + }, + Some(InitMsg::IsAuthTrusted(auth_server)) => { + if global_state + .settings + .networking + .trusted_auth_servers + .contains(&auth_server) + { + // Can't fail since we just polled it, it must be Some + self.client_init + .as_ref() + .unwrap() + .auth_trust(auth_server, true); + } else { + // Show warning that auth server is not trusted and prompt for approval + self.main_menu_ui.auth_trust_prompt(auth_server); + } + }, + None => {}, + } - // Maintain global_state - global_state.maintain(clock.get_last_delta().as_secs_f32()); - - // Maintain the UI. - for event in self - .main_menu_ui - .maintain(global_state, clock.get_last_delta()) - { - match event { - MainMenuEvent::LoginAttempt { + // Maintain the UI. + for event in self + .main_menu_ui + .maintain(global_state, global_state.clock.get_last_delta()) + { + match event { + MainMenuEvent::LoginAttempt { + username, + password, + server_address, + } => { + attempt_login( + global_state, username, password, server_address, - } => { - attempt_login( - global_state, - username, - password, - server_address, - DEFAULT_PORT, - &mut client_init, - ); - }, - MainMenuEvent::CancelLoginAttempt => { - // client_init contains Some(ClientInit), which spawns a thread which - // contains a TcpStream::connect() call This call is - // blocking TODO fix when the network rework happens - global_state.singleplayer = None; - client_init = None; - self.main_menu_ui.cancel_connection(); - }, + DEFAULT_PORT, + &mut self.client_init, + ); + }, + MainMenuEvent::CancelLoginAttempt => { + // client_init contains Some(ClientInit), which spawns a thread which contains a + // TcpStream::connect() call This call is blocking + // TODO fix when the network rework happens #[cfg(feature = "singleplayer")] - MainMenuEvent::StartSingleplayer => { - let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool + { + global_state.singleplayer = None; + } + self.client_init = None; + self.main_menu_ui.cancel_connection(); + }, + #[cfg(feature = "singleplayer")] + MainMenuEvent::StartSingleplayer => { + let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool - global_state.singleplayer = Some(singleplayer); + global_state.singleplayer = Some(singleplayer); - attempt_login( - global_state, - "singleplayer".to_owned(), - "".to_owned(), - server_settings.gameserver_address.ip().to_string(), - server_settings.gameserver_address.port(), - &mut client_init, - ); - }, - MainMenuEvent::Settings => {}, // TODO - MainMenuEvent::Quit => return PlayStateResult::Shutdown, - /*MainMenuEvent::DisclaimerClosed => { - global_state.settings.show_disclaimer = false - },*/ - MainMenuEvent::AuthServerTrust(auth_server, trust) => { - if trust { - global_state - .settings - .networking - .trusted_auth_servers - .insert(auth_server.clone()); - global_state.settings.save_to_file_warn(); - } - client_init - .as_ref() - .map(|init| init.auth_trust(auth_server, trust)); - }, - } + attempt_login( + global_state, + "singleplayer".to_owned(), + "".to_owned(), + server_settings.gameserver_address.ip().to_string(), + server_settings.gameserver_address.port(), + &mut self.client_init, + ); + }, + MainMenuEvent::Settings => {}, // TODO + MainMenuEvent::Quit => return PlayStateResult::Shutdown, + /*MainMenuEvent::DisclaimerClosed => { + global_state.settings.show_disclaimer = false + },*/ + MainMenuEvent::AuthServerTrust(auth_server, trust) => { + if trust { + global_state + .settings + .networking + .trusted_auth_servers + .insert(auth_server.clone()); + global_state.settings.save_to_file_warn(); + } + self.client_init + .as_ref() + .map(|init| init.auth_trust(auth_server, trust)); + }, } - - if let Some(info) = global_state.info_message.take() { - self.main_menu_ui.show_info(info); - } - - // Draw the UI to the screen. - self.main_menu_ui.render(global_state.window.renderer_mut()); - - // Finish the frame. - global_state.window.renderer_mut().flush(); - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers!"); - - // Wait for the next tick - clock.tick(Duration::from_millis( - 1000 / (global_state.settings.graphics.max_fps as u64), - )); } + + if let Some(info) = global_state.info_message.take() { + self.main_menu_ui.show_info(info); + } + + PlayStateResult::Continue } fn name(&self) -> &'static str { "Title" } + + fn render(&mut self, renderer: &mut Renderer, _: &Settings) { + // Draw the UI to the screen. + self.main_menu_ui.render(renderer); + } } fn attempt_login( diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index a8d037f482..e1f17b4440 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -17,7 +17,7 @@ use conrod_core::{ widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; -use rand::{seq::SliceRandom, thread_rng}; +use rand::{seq::SliceRandom, thread_rng, Rng}; use std::time::Duration; const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9); @@ -35,6 +35,7 @@ widget_ids! { alpha_text, banner, banner_top, + gears, // Disclaimer //disc_window, //disc_text_1, @@ -78,6 +79,9 @@ widget_ids! { info_bottom, // Auth Trust Prompt button_add_auth_trust, + // Loading Screen Tips + tip_txt_bg, + tip_txt, } } @@ -98,7 +102,12 @@ image_ids! { button_press: "voxygen.element.buttons.button_press", input_bg: "voxygen.element.misc_bg.textbox_mid", //disclaimer: "voxygen.element.frames.disclaimer", - + // Animation + f1: "voxygen.element.animation.gears.1", + f2: "voxygen.element.animation.gears.2", + f3: "voxygen.element.animation.gears.3", + f4: "voxygen.element.animation.gears.4", + f5: "voxygen.element.animation.gears.5", nothing: (), @@ -107,7 +116,7 @@ image_ids! { rotation_image_ids! { pub struct ImgsRot { - + // Tooltip Test tt_side: "voxygen/element/frames/tt_test_edge", @@ -155,12 +164,14 @@ pub struct MainMenuUi { show_servers: bool, //show_disclaimer: bool, time: f32, + anim_timer: f32, bg_img_id: conrod_core::image::Id, voxygen_i18n: std::sync::Arc, fonts: ConrodVoxygenFonts, + tip_no: u16, } -impl MainMenuUi { +impl<'a> MainMenuUi { pub fn new(global_state: &mut GlobalState) -> Self { let window = &mut global_state.window; let networking = &global_state.settings.networking; @@ -196,6 +207,7 @@ impl MainMenuUi { let bg_img_id = ui.add_graphic(Graphic::Image(load_expect( bg_imgs.choose(&mut rng).unwrap(), ))); + //let chosen_tip = *tips.choose(&mut rng).unwrap(); // Load language let voxygen_i18n = load_expect::(&i18n_asset_key( &global_state.settings.language.selected_language, @@ -221,10 +233,12 @@ impl MainMenuUi { show_servers: false, connect: false, time: 0.0, + anim_timer: 0.0, //show_disclaimer: global_state.settings.show_disclaimer, bg_img_id, voxygen_i18n, fonts, + tip_no: 0, } } @@ -236,6 +250,13 @@ impl MainMenuUi { self.time = self.time + dt.as_secs_f32(); let fade_msg = (self.time * 2.0).sin() * 0.5 + 0.51; let (ref mut ui_widgets, ref mut _tooltip_manager) = self.ui.set_widgets(); + let tip_msg = format!( + "{} {}", + &self.voxygen_i18n.get("main.tip"), + &self.voxygen_i18n.get_variation("loading.tips", self.tip_no), + ); + let tip_show = global_state.settings.gameplay.loading_tips; + let mut rng = thread_rng(); let version = format!( "{}-{}", env!("CARGO_PKG_VERSION"), @@ -243,6 +264,7 @@ impl MainMenuUi { ); const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); const TEXT_COLOR_2: Color = Color::Rgba(1.0, 1.0, 1.0, 0.2); + const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); //const INACTIVE: Color = Color::Rgba(0.47, 0.47, 0.47, 0.47); let intro_text = &self.voxygen_i18n.get("main.login_process"); @@ -275,6 +297,37 @@ impl MainMenuUi { .middle_of(ui_widgets.window) .set(self.ids.bg, ui_widgets); + if self.connect { + self.anim_timer = (self.anim_timer + dt.as_secs_f32()) * 1.05; // Linear time function with Anim-Speed Factor + if self.anim_timer >= 4.0 { + self.anim_timer = 0.0 // Reset timer at last frame to loop + }; + Image::new(match self.anim_timer.round() as i32 { + 0 => self.imgs.f1, + 1 => self.imgs.f2, + 2 => self.imgs.f3, + 3 => self.imgs.f4, + _ => self.imgs.f5, + }) + .w_h(74.0, 62.0) + .bottom_left_with_margins_on(self.ids.bg, 10.0, 10.0) + .set(self.ids.gears, ui_widgets); + if tip_show { + Text::new(&tip_msg) + .color(TEXT_BG) + .mid_bottom_with_margin_on(ui_widgets.window, 80.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(20)) + .set(self.ids.tip_txt_bg, ui_widgets); + Text::new(&tip_msg) + .color(TEXT_COLOR) + .bottom_left_with_margins_on(self.ids.tip_txt_bg, 2.0, 2.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(20)) + .set(self.ids.tip_txt, ui_widgets); + }; + }; + // Version displayed top right corner Text::new(&version) .color(TEXT_COLOR) @@ -333,16 +386,16 @@ impl MainMenuUi { .middle_of(self.ids.login_error_bg) .set(self.ids.error_frame, ui_widgets); if let PopupType::ConnectionInfo = popup_type { - text.mid_top_with_margin_on(self.ids.error_frame, 10.0) - .font_id(self.fonts.alkhemi.conrod_id) - .bottom_left_with_margins_on(ui_widgets.window, 60.0, 60.0) - .font_size(self.fonts.cyri.scale(70)) - .set(self.ids.login_error, ui_widgets); + /*text.mid_top_with_margin_on(self.ids.error_frame, 10.0) + .font_id(self.fonts.cyri.conrod_id) + .bottom_left_with_margins_on(self.ids.bg, 30.0, 95.0) + .font_size(self.fonts.cyri.scale(35)) + .set(self.ids.login_error, ui_widgets);*/ } else { text.mid_top_with_margin_on(self.ids.error_frame, 10.0) .w(frame_w - 10.0 * 2.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(25)) + .font_size(self.fonts.cyri.scale(20)) .set(self.ids.login_error, ui_widgets); }; if Button::image(self.imgs.button) @@ -508,7 +561,7 @@ impl MainMenuUi { self.connect = true; self.connecting = Some(std::time::Instant::now()); self.popup = Some(PopupData { - msg: [self.voxygen_i18n.get("main.creating_world"), "..."].concat(), + msg: [self.voxygen_i18n.get(""), ""].concat(), popup_type: PopupType::ConnectionInfo, }); }; @@ -691,6 +744,7 @@ impl MainMenuUi { .set(self.ids.login_button, ui_widgets) .was_clicked() { + self.tip_no = rng.gen(); login!(); } @@ -712,6 +766,7 @@ impl MainMenuUi { .set(self.ids.singleplayer_button, ui_widgets) .was_clicked() { + self.tip_no = rng.gen(); singleplayer!(); } } diff --git a/voxygen/src/render/error.rs b/voxygen/src/render/error.rs index efe9110bff..8c9352857d 100644 --- a/voxygen/src/render/error.rs +++ b/voxygen/src/render/error.rs @@ -18,63 +18,12 @@ impl From> for RenderError { } impl From> for RenderError { - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 fn from(err: gfx::PipelineStateError<&str>) -> Self { - // This is horrid. We do it to get rid of the `&str`'s lifetime bound by turning - // it into a `String`. match err { gfx::PipelineStateError::DescriptorInit(err) => { - gfx::PipelineStateError::DescriptorInit(match err { - gfx::pso::InitError::VertexImport(s, x) => { - gfx::pso::InitError::VertexImport(s.to_string(), x) - }, - gfx::pso::InitError::ConstantBuffer(s, x) => { - gfx::pso::InitError::ConstantBuffer( - s.to_string(), - x.map(|x| match x { - gfx::pso::ElementError::NotFound(s) => { - gfx::pso::ElementError::NotFound(s.to_string()) - }, - gfx::pso::ElementError::Offset { - name, - shader_offset, - code_offset, - } => gfx::pso::ElementError::Offset { - name: name.to_string(), - shader_offset, - code_offset, - }, - gfx::pso::ElementError::Format { - name, - shader_format, - code_format, - } => gfx::pso::ElementError::Format { - name: name.to_string(), - shader_format, - code_format, - }, - }), - ) - }, - gfx::pso::InitError::GlobalConstant(s, x) => { - gfx::pso::InitError::GlobalConstant(s.to_string(), x) - }, - gfx::pso::InitError::ResourceView(s, x) => { - gfx::pso::InitError::ResourceView(s.to_string(), x) - }, - gfx::pso::InitError::UnorderedView(s, x) => { - gfx::pso::InitError::UnorderedView(s.to_string(), x) - }, - gfx::pso::InitError::Sampler(s, x) => { - gfx::pso::InitError::Sampler(s.to_string(), x) - }, - gfx::pso::InitError::PixelExport(s, x) => { - gfx::pso::InitError::PixelExport(s.to_string(), x) - }, - }) + gfx::PipelineStateError::DescriptorInit(err) }, - gfx::PipelineStateError::Program(p) => gfx::PipelineStateError::Program(p), - gfx::PipelineStateError::DeviceCreate(c) => gfx::PipelineStateError::DeviceCreate(c), + err => err, } .into() } diff --git a/voxygen/src/run.rs b/voxygen/src/run.rs new file mode 100644 index 0000000000..9d68f02ef1 --- /dev/null +++ b/voxygen/src/run.rs @@ -0,0 +1,155 @@ +use crate::{ + menu::main::MainMenuState, + ui, + window::{Event, EventLoop}, + Direction, GlobalState, PlayState, PlayStateResult, +}; +use std::{mem, time::Duration}; +use tracing::debug; + +pub fn run(mut global_state: GlobalState, event_loop: EventLoop) { + // Set up the initial play state. + let mut states: Vec> = vec![Box::new(MainMenuState::new(&mut global_state))]; + states.last_mut().map(|current_state| { + current_state.enter(&mut global_state, Direction::Forwards); + let current_state = current_state.name(); + debug!(?current_state, "Started game with state"); + }); + + // Used to ignore every other `MainEventsCleared` + // This is a workaround for a bug on macos in which mouse motion events are only + // reported every other cycle of the event loop + // See: https://github.com/rust-windowing/winit/issues/1418 + let mut polled_twice = false; + + event_loop.run(move |event, _, control_flow| { + // Continously run loop since we handle sleeping + *control_flow = winit::event_loop::ControlFlow::Poll; + + // Get events for the ui. + if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) { + global_state.window.send_event(Event::Ui(event)); + } + + match event { + winit::event::Event::MainEventsCleared => { + if polled_twice { + handle_main_events_cleared(&mut states, control_flow, &mut global_state); + } + polled_twice = !polled_twice; + }, + winit::event::Event::WindowEvent { event, .. } => global_state + .window + .handle_window_event(event, &mut global_state.settings), + winit::event::Event::DeviceEvent { event, .. } => { + global_state.window.handle_device_event(event) + }, + winit::event::Event::LoopDestroyed => { + // Save any unsaved changes to settings and profile + global_state.settings.save_to_file_warn(); + global_state.profile.save_to_file_warn(); + }, + _ => {}, + } + }); +} + +fn handle_main_events_cleared( + states: &mut Vec>, + control_flow: &mut winit::event_loop::ControlFlow, + global_state: &mut GlobalState, +) { + // Screenshot / Fullscreen toggle + global_state + .window + .resolve_deduplicated_events(&mut global_state.settings); + // Run tick here + + // What's going on here? + // --------------------- + // The state system used by Voxygen allows for the easy development of + // stack-based menus. For example, you may want a "title" state + // that can push a "main menu" state on top of it, which can in + // turn push a "settings" state or a "game session" state on top of it. + // The code below manages the state transfer logic automatically so that we + // don't have to re-engineer it for each menu we decide to add + // to the game. + let mut exit = true; + while let Some(state_result) = states.last_mut().map(|last| { + let events = global_state.window.fetch_events(); + last.tick(global_state, events) + }) { + // Implement state transfer logic. + match state_result { + PlayStateResult::Continue => { + // Wait for the next tick. + global_state.clock.tick(Duration::from_millis( + 1000 / global_state.settings.graphics.max_fps as u64, + )); + + // Maintain global state. + global_state.maintain(global_state.clock.get_last_delta().as_secs_f32()); + + exit = false; + break; + }, + PlayStateResult::Shutdown => { + debug!("Shutting down all states..."); + while states.last().is_some() { + states.pop().map(|old_state| { + debug!("Popped state '{}'.", old_state.name()); + global_state.on_play_state_changed(); + }); + } + }, + PlayStateResult::Pop => { + states.pop().map(|old_state| { + debug!("Popped state '{}'.", old_state.name()); + global_state.on_play_state_changed(); + }); + states.last_mut().map(|new_state| { + new_state.enter(global_state, Direction::Backwards); + }); + }, + PlayStateResult::Push(mut new_state) => { + new_state.enter(global_state, Direction::Forwards); + debug!("Pushed state '{}'.", new_state.name()); + states.push(new_state); + global_state.on_play_state_changed(); + }, + PlayStateResult::Switch(mut new_state) => { + new_state.enter(global_state, Direction::Forwards); + states.last_mut().map(|old_state| { + debug!( + "Switching to state '{}' from state '{}'.", + new_state.name(), + old_state.name() + ); + mem::swap(old_state, &mut new_state); + global_state.on_play_state_changed(); + }); + }, + } + } + + if exit { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + + if let Some(last) = states.last_mut() { + let renderer = global_state.window.renderer_mut(); + // Clear the shadow maps. + renderer.clear_shadows(); + // Clear the screen + renderer.clear(); + // Render the screen using the global renderer + last.render(renderer, &global_state.settings); + // Finish the frame. + global_state.window.renderer_mut().flush(); + // Display the frame on the window. + global_state + .window + .swap_buffers() + .expect("Failed to swap window buffers!"); + } +} diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index a2cbd6b895..69937ed556 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -8,8 +8,8 @@ use anim::Skeleton; use common::{ assets::watch::ReloadIndicator, comp::{ - item::{tool::ToolKind, ItemKind}, - Body, CharacterState, Item, Loadout, + item::{armor::ArmorKind, tool::ToolKind, ItemKind, LanternKind}, + Body, CharacterState, Loadout, }, figure::Segment, vol::BaseVol, @@ -35,14 +35,14 @@ struct CharacterCacheKey { state: Option>, // TODO: Can this be simplified? active_tool: Option, second_tool: Option, - shoulder: Option, - chest: Option, - belt: Option, - back: Option, - lantern: Option, - hand: Option, - pants: Option, - foot: Option, + shoulder: Option, + chest: Option, + belt: Option, + back: Option, + lantern: Option, + hand: Option, + pants: Option, + foot: Option, } impl CharacterCacheKey { @@ -63,14 +63,50 @@ impl CharacterCacheKey { } else { None }, - shoulder: loadout.shoulder.clone(), - chest: loadout.chest.clone(), - belt: loadout.belt.clone(), - back: loadout.back.clone(), - lantern: loadout.lantern.clone(), - hand: loadout.hand.clone(), - pants: loadout.pants.clone(), - foot: loadout.foot.clone(), + shoulder: if let Some(ItemKind::Armor(armor)) = + loadout.shoulder.as_ref().map(|i| &i.kind) + { + Some(armor.kind) + } else { + None + }, + chest: if let Some(ItemKind::Armor(armor)) = loadout.chest.as_ref().map(|i| &i.kind) { + Some(armor.kind) + } else { + None + }, + belt: if let Some(ItemKind::Armor(armor)) = loadout.belt.as_ref().map(|i| &i.kind) { + Some(armor.kind) + } else { + None + }, + back: if let Some(ItemKind::Armor(armor)) = loadout.back.as_ref().map(|i| &i.kind) { + Some(armor.kind) + } else { + None + }, + lantern: if let Some(ItemKind::Lantern(lantern)) = + loadout.lantern.as_ref().map(|i| &i.kind) + { + Some(lantern.kind) + } else { + None + }, + hand: if let Some(ItemKind::Armor(armor)) = loadout.hand.as_ref().map(|i| &i.kind) { + Some(armor.kind) + } else { + None + }, + pants: if let Some(ItemKind::Armor(armor)) = loadout.pants.as_ref().map(|i| &i.kind) { + Some(armor.kind) + } else { + None + }, + foot: if let Some(ItemKind::Armor(armor)) = loadout.foot.as_ref().map(|i| &i.kind) { + Some(armor.kind) + } else { + None + }, } } } diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index be0ec5c4b6..3d634b0693 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -11,7 +11,9 @@ use common::{ golem::{BodyType as GBodyType, Species as GSpecies}, humanoid::{Body, BodyType, EyeColor, Skin, Species}, item::{ - armor::{Armor, Back, Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Tabard}, + armor::{ + Armor, ArmorKind, Back, Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Tabard, + }, tool::{Tool, ToolKind}, ItemKind, Lantern, LanternKind, }, @@ -365,10 +367,10 @@ impl HumArmorShoulderSpec { flipped: bool, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Shoulder(shoulder), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Shoulder(shoulder), .. - }) = loadout.shoulder.as_ref().map(|i| &i.kind) + })) = loadout.shoulder.as_ref().map(|i| &i.kind) { match self.0.map.get(&shoulder) { Some(spec) => spec, @@ -447,10 +449,10 @@ impl HumArmorChestSpec { loadout: &Loadout, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Chest(chest), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Chest(chest), .. - }) = loadout.chest.as_ref().map(|i| &i.kind) + })) = loadout.chest.as_ref().map(|i| &i.kind) { match self.0.map.get(&chest) { Some(spec) => spec, @@ -504,10 +506,10 @@ impl HumArmorHandSpec { flipped: bool, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Hand(hand), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Hand(hand), .. - }) = loadout.hand.as_ref().map(|i| &i.kind) + })) = loadout.hand.as_ref().map(|i| &i.kind) { match self.0.map.get(&hand) { Some(spec) => spec, @@ -580,10 +582,10 @@ impl HumArmorBeltSpec { loadout: &Loadout, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Belt(belt), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Belt(belt), .. - }) = loadout.belt.as_ref().map(|i| &i.kind) + })) = loadout.belt.as_ref().map(|i| &i.kind) { match self.0.map.get(&belt) { Some(spec) => spec, @@ -624,10 +626,10 @@ impl HumArmorBackSpec { loadout: &Loadout, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Back(back), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Back(back), .. - }) = loadout.back.as_ref().map(|i| &i.kind) + })) = loadout.back.as_ref().map(|i| &i.kind) { match self.0.map.get(&back) { Some(spec) => spec, @@ -667,10 +669,10 @@ impl HumArmorPantsSpec { loadout: &Loadout, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Pants(pants), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Pants(pants), .. - }) = loadout.pants.as_ref().map(|i| &i.kind) + })) = loadout.pants.as_ref().map(|i| &i.kind) { match self.0.map.get(&pants) { Some(spec) => spec, @@ -724,10 +726,10 @@ impl HumArmorFootSpec { flipped: bool, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Foot(foot), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Foot(foot), .. - }) = loadout.foot.as_ref().map(|i| &i.kind) + })) = loadout.foot.as_ref().map(|i| &i.kind) { match self.0.map.get(&foot) { Some(spec) => spec, @@ -879,10 +881,10 @@ impl HumArmorHeadSpec { loadout: &Loadout, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Head(head), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Head(head), .. - }) = loadout.head.as_ref().map(|i| &i.kind) + })) = loadout.head.as_ref().map(|i| &i.kind) { match self.0.map.get(&head) { Some(spec) => spec, @@ -934,10 +936,10 @@ impl HumArmorTabardSpec { loadout: &Loadout, generate_mesh: impl FnOnce(Segment, Vec3) -> BoneMeshes, ) -> BoneMeshes { - let spec = if let Some(ItemKind::Armor { - kind: Armor::Tabard(tabard), + let spec = if let Some(ItemKind::Armor(Armor { + kind: ArmorKind::Tabard(tabard), .. - }) = loadout.tabard.as_ref().map(|i| &i.kind) + })) = loadout.tabard.as_ref().map(|i| &i.kind) { match self.0.map.get(&tabard) { Some(spec) => spec, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index b547104573..3484605a86 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -790,6 +790,32 @@ impl FigureMgr { ) } }, + CharacterState::ChargedRanged(data) => { + if data.exhausted { + anim::character::ShootAnimation::update_skeleton( + &target_base, + (active_tool_kind, second_tool_kind, vel.0.magnitude(), time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ) + } else { + anim::character::ChargeAnimation::update_skeleton( + &target_base, + ( + active_tool_kind, + second_tool_kind, + vel.0.magnitude(), + ori, + state.last_ori, + time, + ), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ) + } + }, CharacterState::Boost(_) => { anim::character::AlphaAnimation::update_skeleton( &target_base, @@ -2724,7 +2750,7 @@ impl FigureState { let smoothing = (5.0 * dt).min(1.0); if let Some(last_pos) = self.last_pos { - self.avg_vel = (1.0 - smoothing) * self.avg_vel + smoothing * (pos - last_pos) * dt; + self.avg_vel = (1.0 - smoothing) * self.avg_vel + smoothing * (pos - last_pos) / dt; } self.last_pos = Some(pos); } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 5c9f532361..f3f3dfe9db 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -360,6 +360,18 @@ fn sprite_config_for(kind: BlockKind) -> Option { variations: 2, wind_sway: 0.0, }), + BlockKind::Stones => Some(SpriteConfig { + variations: 3, + wind_sway: 0.0, + }), + BlockKind::Twigs => Some(SpriteConfig { + variations: 3, + wind_sway: 0.0, + }), + BlockKind::ShinyGem => Some(SpriteConfig { + variations: 3, + wind_sway: 0.0, + }), _ => None, } } @@ -2235,6 +2247,63 @@ impl Terrain { Vec3::new(-6.0, -10.5, 0.0), Vec3::one(), ), + /* Stones */ + make_models( + (BlockKind::Stones, 0), + "voxygen.voxel.sprite.rocks.rock-0", + Vec3::new(-3.0, -3.5, 0.0), + Vec3::one(), + ), + make_models( + (BlockKind::Stones, 1), + "voxygen.voxel.sprite.rocks.rock-1", + Vec3::new(-4.5, -5.5, 0.0), + Vec3::one(), + ), + make_models( + (BlockKind::Stones, 2), + "voxygen.voxel.sprite.rocks.rock-2", + Vec3::new(-4.5, -4.5, 0.0), + Vec3::one(), + ), + /* Twigs */ + make_models( + (BlockKind::Twigs, 0), + "voxygen.voxel.sprite.twigs.twigs-0", + Vec3::new(-3.5, -3.5, 0.0), + Vec3::one(), + ), + make_models( + (BlockKind::Twigs, 1), + "voxygen.voxel.sprite.twigs.twigs-1", + Vec3::new(-2.0, -1.5, 0.0), + Vec3::one(), + ), + make_models( + (BlockKind::Twigs, 2), + "voxygen.voxel.sprite.twigs.twigs-2", + Vec3::new(-4.0, -4.0, 0.0), + Vec3::one(), + ), + // Shiny Gems + make_models( + (BlockKind::ShinyGem, 0), + "voxygen.voxel.sprite.gem.gem_blue", + Vec3::new(-2.0, -3.0, 0.0), + Vec3::one(), + ), + make_models( + (BlockKind::ShinyGem, 1), + "voxygen.voxel.sprite.gem.gem_green", + Vec3::new(-2.0, -3.0, 0.0), + Vec3::one(), + ), + make_models( + (BlockKind::ShinyGem, 2), + "voxygen.voxel.sprite.gem.gem_red", + Vec3::new(-3.0, -2.0, -2.0), + Vec3::one(), + ), ] .into_iter() .collect(); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index aad1e43e90..8dff4d6335 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -5,17 +5,20 @@ use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, key_state::KeyState, menu::char_selection::CharSelectionState, + render::Renderer, scene::{camera, Scene, SceneData}, - settings::AudioOutput, + settings::{AudioOutput, ControlSettings, Settings}, window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; use common::{ - assets::{load_expect, load_watched, watch}, - clock::Clock, + assets::{load_expect, load_watched}, comp, - comp::{ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_PICKUP_RANGE_SQR}, + comp::{ + ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_MOUNT_RANGE_SQR, + MAX_PICKUP_RANGE_SQR, + }, event::EventBus, msg::ClientState, terrain::{Block, BlockKind}, @@ -43,6 +46,12 @@ pub struct SessionState { inputs: comp::ControllerInputs, selected_block: Block, voxygen_i18n: std::sync::Arc, + walk_forward_dir: Vec2, + walk_right_dir: Vec2, + freefly_vel: Vec3, + free_look: bool, + auto_walk: bool, + is_aiming: bool, } /// Represents an active game session (i.e., the one being played). @@ -60,14 +69,12 @@ impl SessionState { .camera_mut() .set_fov_deg(global_state.settings.graphics.fov); let hud = Hud::new(global_state, &client.borrow()); - { - let mut client = client.borrow_mut(); - let my_entity = client.entity(); - client.state_mut().ecs_mut().insert(MyEntity(my_entity)); - } let voxygen_i18n = load_expect::(&i18n_asset_key( &global_state.settings.language.selected_language, )); + + let walk_forward_dir = scene.camera().forward_xy(); + let walk_right_dir = scene.camera().right_xy(); Self { scene, client, @@ -76,8 +83,20 @@ impl SessionState { hud, selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)), voxygen_i18n, + walk_forward_dir, + walk_right_dir, + freefly_vel: Vec3::zero(), + free_look: false, + auto_walk: false, + is_aiming: false, } } + + fn stop_auto_walk(&mut self) { + self.auto_walk = false; + self.hud.auto_walk(false); + self.key_state.auto_walk = false; + } } impl SessionState { @@ -87,9 +106,6 @@ impl SessionState { let mut client = self.client.borrow_mut(); for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? { - self.voxygen_i18n = load_expect::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )); match event { client::Event::Chat(m) => { self.hud.new_message(m); @@ -154,12 +170,10 @@ impl SessionState { } impl PlayState for SessionState { - fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult { + fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { // Trap the cursor. global_state.window.grab_cursor(true); - // Set up an fps clock. - let mut clock = Clock::start(); self.client.borrow_mut().clear_terrain(); // Send startup commands to the server @@ -168,30 +182,28 @@ impl PlayState for SessionState { self.client.borrow_mut().send_chat(cmd.to_string()); } } + } - // Keep a watcher on the language - let mut localization_watcher = watch::ReloadIndicator::new(); - let mut localized_strings = load_watched::( - &i18n_asset_key(&global_state.settings.language.selected_language), - &mut localization_watcher, - ) - .unwrap(); + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { + self.voxygen_i18n = load_expect::(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); - let mut walk_forward_dir = self.scene.camera().forward_xy(); - let mut walk_right_dir = self.scene.camera().right_xy(); - let mut freefly_vel = Vec3::zero(); - let mut free_look = false; - let mut auto_walk = false; + // TODO: can this be a method on the session or are there borrowcheck issues? - fn stop_auto_walk(auto_walk: &mut bool, key_state: &mut KeyState, hud: &mut Hud) { - *auto_walk = false; - hud.auto_walk(false); - key_state.auto_walk = false; - } - - // Game loop - let mut current_client_state = self.client.borrow().get_client_state(); - while let ClientState::Pending | ClientState::Character = current_client_state { + let client_state = self.client.borrow().get_client_state(); + if let ClientState::Pending | ClientState::Character = client_state { + // Update MyEntity + // Note: Alternatively, the client could emit an event when the entity changes + // which may or may not be more elegant + { + let my_entity = self.client.borrow().entity(); + self.client + .borrow_mut() + .state_mut() + .ecs_mut() + .insert(MyEntity(my_entity)); + } // Compute camera data self.scene .camera_mut() @@ -233,6 +245,7 @@ impl PlayState for SessionState { }, ) }; + self.is_aiming = is_aiming; let cam_dir: Vec3 = Vec3::from( (view_mat/* * Mat4::translation_3d(-focus_off */).inverted() * -Vec4::unit_z(), @@ -284,7 +297,7 @@ impl PlayState for SessionState { })); // Handle window events. - for event in global_state.window.fetch_events(&mut global_state.settings) { + for event in events { // Pass all events to the ui first. if self.hud.handle_event(event.clone(), global_state) { continue; @@ -337,6 +350,7 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::Respawn, state) if state != self.key_state.respawn => { + self.stop_auto_walk(); self.key_state.respawn = state; if state { self.client.borrow_mut().respawn(); @@ -353,7 +367,7 @@ impl PlayState for SessionState { { self.key_state.toggle_sit = state; if state { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); self.client.borrow_mut().toggle_sit(); } } @@ -362,31 +376,31 @@ impl PlayState for SessionState { { self.key_state.toggle_dance = state; if state { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); self.client.borrow_mut().toggle_dance(); } } Event::InputUpdate(GameInput::MoveForward, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.up = state }, Event::InputUpdate(GameInput::MoveBack, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.down = state }, Event::InputUpdate(GameInput::MoveLeft, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.left = state }, Event::InputUpdate(GameInput::MoveRight, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.right = state }, @@ -438,25 +452,36 @@ impl PlayState for SessionState { .copied(); if let Some(player_pos) = player_pos { // Find closest mountable entity - let closest_mountable = ( + let mut closest_mountable: Option<(specs::Entity, i32)> = None; + + for (entity, pos, ms) in ( &client.state().ecs().entities(), &client.state().ecs().read_storage::(), &client.state().ecs().read_storage::(), ) .join() - .filter(|(_, _, ms)| { - if let comp::MountState::Unmounted = ms { - true - } else { - false - } - }) - .min_by_key(|(_, pos, _)| { - (player_pos.0.distance_squared(pos.0) * 1000.0) as i32 - }) - .map(|(uid, _, _)| uid); + .filter(|(entity, _, _)| *entity != client.entity()) + { + if comp::MountState::Unmounted != *ms { + continue; + } - if let Some(mountee_entity) = closest_mountable { + let dist = + (player_pos.0.distance_squared(pos.0) * 1000.0) as i32; + if dist > MAX_MOUNT_RANGE_SQR { + continue; + } + + if let Some(previous) = closest_mountable.as_mut() { + if dist < previous.1 { + *previous = (entity, dist); + } + } else { + closest_mountable = Some((entity, dist)); + } + } + + if let Some((mountee_entity, _)) = closest_mountable { client.mount(mountee_entity); } } @@ -503,12 +528,12 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::FreeLook, state) => { match (global_state.settings.gameplay.free_look_behavior, state) { (PressBehavior::Toggle, true) => { - free_look = !free_look; - self.hud.free_look(free_look); + self.free_look = !self.free_look; + self.hud.free_look(self.free_look); }, (PressBehavior::Hold, state) => { - free_look = state; - self.hud.free_look(free_look); + self.free_look = state; + self.hud.free_look(self.free_look); }, _ => {}, }; @@ -516,14 +541,14 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::AutoWalk, state) => { match (global_state.settings.gameplay.auto_walk_behavior, state) { (PressBehavior::Toggle, true) => { - auto_walk = !auto_walk; - self.key_state.auto_walk = auto_walk; - self.hud.auto_walk(auto_walk); + self.auto_walk = !self.auto_walk; + self.key_state.auto_walk = self.auto_walk; + self.hud.auto_walk(self.auto_walk); }, (PressBehavior::Hold, state) => { - auto_walk = state; - self.key_state.auto_walk = auto_walk; - self.hud.auto_walk(auto_walk); + self.auto_walk = state; + self.key_state.auto_walk = self.auto_walk; + self.hud.auto_walk(self.auto_walk); }, _ => {}, } @@ -561,9 +586,9 @@ impl PlayState for SessionState { } } - if !free_look { - walk_forward_dir = self.scene.camera().forward_xy(); - walk_right_dir = self.scene.camera().right_xy(); + if !self.free_look { + self.walk_forward_dir = self.scene.camera().forward_xy(); + self.walk_right_dir = self.scene.camera().right_xy(); self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap(); } @@ -575,8 +600,9 @@ impl PlayState for SessionState { camera::CameraMode::FirstPerson | camera::CameraMode::ThirdPerson => { // Move the player character based on their walking direction. // This could be different from the camera direction if free look is enabled. - self.inputs.move_dir = walk_right_dir * axis_right + walk_forward_dir * axis_up; - freefly_vel = Vec3::zero(); + self.inputs.move_dir = + self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up; + self.freefly_vel = Vec3::zero(); }, camera::CameraMode::Freefly => { @@ -590,27 +616,27 @@ impl PlayState for SessionState { let right = self.scene.camera().right(); let dir = right * axis_right + forward * axis_up; - let dt = clock.get_last_delta().as_secs_f32(); - if freefly_vel.magnitude_squared() > 0.01 { - let new_vel = - freefly_vel - freefly_vel.normalized() * (FREEFLY_DAMPING * dt); - if freefly_vel.dot(new_vel) > 0.0 { - freefly_vel = new_vel; + let dt = global_state.clock.get_last_delta().as_secs_f32(); + if self.freefly_vel.magnitude_squared() > 0.01 { + let new_vel = self.freefly_vel + - self.freefly_vel.normalized() * (FREEFLY_DAMPING * dt); + if self.freefly_vel.dot(new_vel) > 0.0 { + self.freefly_vel = new_vel; } else { - freefly_vel = Vec3::zero(); + self.freefly_vel = Vec3::zero(); } } if dir.magnitude_squared() > 0.01 { - freefly_vel += dir * (FREEFLY_ACCEL * dt); - if freefly_vel.magnitude() > FREEFLY_MAX_SPEED { - freefly_vel = freefly_vel.normalized() * FREEFLY_MAX_SPEED; + self.freefly_vel += dir * (FREEFLY_ACCEL * dt); + if self.freefly_vel.magnitude() > FREEFLY_MAX_SPEED { + self.freefly_vel = self.freefly_vel.normalized() * FREEFLY_MAX_SPEED; } } let pos = self.scene.camera().get_focus_pos(); self.scene .camera_mut() - .set_focus_pos(pos + freefly_vel * dt); + .set_focus_pos(pos + self.freefly_vel * dt); // Do not apply any movement to the player character self.inputs.move_dir = Vec2::zero(); @@ -620,16 +646,14 @@ impl PlayState for SessionState { self.inputs.climb = self.key_state.climb(); // Runs if either in a multiplayer server or the singleplayer server is unpaused - if global_state.singleplayer.is_none() - || !global_state.singleplayer.as_ref().unwrap().is_paused() - { + if !global_state.paused() { // Perform an in-game tick. - match self.tick(clock.get_avg_delta(), global_state) { + match self.tick(global_state.clock.get_avg_delta(), global_state) { Ok(TickAction::Continue) => {}, // Do nothing Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu Err(err) => { global_state.info_message = - Some(localized_strings.get("common.connection_lost").to_owned()); + Some(self.voxygen_i18n.get("common.connection_lost").to_owned()); error!("[session] Failed to tick the scene: {:?}", err); return PlayStateResult::Pop; @@ -637,9 +661,6 @@ impl PlayState for SessionState { } } - // Maintain global state. - global_state.maintain(clock.get_last_delta().as_secs_f32()); - // Recompute dependents just in case some input modified the camera self.scene .camera_mut() @@ -649,7 +670,7 @@ impl PlayState for SessionState { // as well avoid it unless we need it). let debug_info = if global_state.settings.gameplay.toggle_debug { Some(DebugInfo { - tps: clock.get_tps(), + tps: global_state.clock.get_tps(), ping_ms: self.client.borrow().get_ping_ms_rolling_avg(), coordinates: self .client @@ -691,7 +712,7 @@ impl PlayState for SessionState { global_state, &debug_info, &self.scene.camera(), - clock.get_last_delta(), + global_state.clock.get_last_delta(), HudInfo { is_aiming, is_first_person: matches!( @@ -702,8 +723,8 @@ impl PlayState for SessionState { ); // Look for changes in the localization files - if localization_watcher.reloaded() { - hud_events.push(HudEvent::ChangeLanguage(localized_strings.metadata.clone())); + if global_state.localization_watcher.reloaded() { + hud_events.push(HudEvent::ChangeLanguage(self.voxygen_i18n.metadata.clone())); } // Maintain the UI. @@ -743,6 +764,10 @@ impl PlayState for SessionState { global_state.settings.gameplay.sct_player_batch = sct_player_batch; global_state.settings.save_to_file_warn(); }, + HudEvent::ToggleTips(loading_tips) => { + global_state.settings.gameplay.loading_tips = loading_tips; + global_state.settings.save_to_file_warn(); + }, HudEvent::SctDamageBatch(sct_damage_batch) => { global_state.settings.gameplay.sct_damage_batch = sct_damage_batch; global_state.settings.save_to_file_warn(); @@ -906,13 +931,13 @@ impl PlayState for SessionState { HudEvent::ChangeLanguage(new_language) => { global_state.settings.language.selected_language = new_language.language_identifier; - localized_strings = load_watched::( + self.voxygen_i18n = load_watched::( &i18n_asset_key(&global_state.settings.language.selected_language), - &mut localization_watcher, + &mut global_state.localization_watcher, ) .unwrap(); - localized_strings.log_missing_entries(); - self.hud.update_language(localized_strings.clone()); + self.voxygen_i18n.log_missing_entries(); + self.hud.update_language(self.voxygen_i18n.clone()); }, HudEvent::ToggleFullscreen => { global_state @@ -927,6 +952,10 @@ impl PlayState for SessionState { HudEvent::ChangeBinding(game_input) => { global_state.window.set_keybinding_mode(game_input); }, + HudEvent::ResetBindings => { + global_state.settings.controls = ControlSettings::default(); + global_state.settings.save_to_file_warn(); + }, HudEvent::ChangeFreeLookBehavior(behavior) => { global_state.settings.gameplay.free_look_behavior = behavior; }, @@ -936,6 +965,9 @@ impl PlayState for SessionState { HudEvent::ChangeStopAutoWalkOnInput(state) => { global_state.settings.gameplay.stop_auto_walk_on_input = state; }, + HudEvent::CraftRecipe(r) => { + self.client.borrow_mut().craft_recipe(&r); + }, } } @@ -961,68 +993,62 @@ impl PlayState for SessionState { }; // Runs if either in a multiplayer server or the singleplayer server is unpaused - if global_state.singleplayer.is_none() - || !global_state.singleplayer.as_ref().unwrap().is_paused() - { + if !global_state.paused() { self.scene.maintain( global_state.window.renderer_mut(), &mut global_state.audio, &scene_data, ); } - - let renderer = global_state.window.renderer_mut(); - - // Clear the shadow maps. - renderer.clear_shadows(); - // Clear the screen - renderer.clear(); - // Render the screen using the global renderer - self.scene.render( - renderer, - client.state(), - client.entity(), - client.get_tick(), - &scene_data, - ); - // Draw the UI to the screen - self.hud.render(renderer, self.scene.globals()); - // Finish the frame - renderer.flush(); } - // Display the frame on the window. - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers!"); - - /* let renderer = global_state.window.renderer_mut(); - // Clear the shadow maps. - renderer.clear_shadows(); - // Clear the screen - renderer.clear(); */ - - // Wait for the next tick. - clock.tick(Duration::from_millis( - 1000 / global_state.settings.graphics.max_fps as u64, - )); - // Clean things up after the tick. self.cleanup(); - current_client_state = self.client.borrow().get_client_state(); - } - - if let ClientState::Registered = current_client_state { - return PlayStateResult::Switch(Box::new(CharSelectionState::new( + PlayStateResult::Continue + } else if let ClientState::Registered = client_state { + PlayStateResult::Switch(Box::new(CharSelectionState::new( global_state, self.client.clone(), - ))); + ))) + } else { + error!("Client not in the expected state, exiting session play state"); + PlayStateResult::Pop } - - PlayStateResult::Pop } fn name(&self) -> &'static str { "Session" } + + /// Render the session to the screen. + /// + /// This method should be called once per frame. + fn render(&mut self, renderer: &mut Renderer, settings: &Settings) { + // Render the screen using the global renderer + { + let client = self.client.borrow(); + + let scene_data = SceneData { + state: client.state(), + player_entity: client.entity(), + loaded_distance: client.loaded_distance(), + view_distance: client.view_distance().unwrap_or(1), + tick: client.get_tick(), + thread_pool: client.thread_pool(), + gamma: settings.graphics.gamma, + mouse_smoothing: settings.gameplay.smooth_pan_enable, + sprite_render_distance: settings.graphics.sprite_render_distance as f32, + figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32, + is_aiming: self.is_aiming, + }; + self.scene.render( + renderer, + client.state(), + client.entity(), + client.get_tick(), + &scene_data, + ); + } + // Draw the UI to the screen + self.hud.render(renderer, self.scene.globals()); + } } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index e282a13b98..65915a5e09 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -6,11 +6,11 @@ use crate::{ window::{GameInput, KeyMouse}, }; use directories_next::{ProjectDirs, UserDirs}; -use glutin::{MouseButton, VirtualKeyCode}; use hashbrown::{HashMap, HashSet}; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; use tracing::warn; +use winit::event::{MouseButton, VirtualKeyCode}; // ControlSetting-like struct used by Serde, to handle not serializing/building // post-deserializing the inverse_keybindings hashmap @@ -100,9 +100,23 @@ impl ControlSettings { self.keybindings.insert(game_input, key_mouse); } + /// Return true if this key is used for multiple GameInputs that aren't + /// expected to be safe to have bound to the same key at the same time + pub fn has_conflicting_bindings(&self, key_mouse: KeyMouse) -> bool { + if let Some(game_inputs) = self.inverse_keybindings.get(&key_mouse) { + for a in game_inputs.iter() { + for b in game_inputs.iter() { + if !GameInput::can_share_bindings(*a, *b) { + return true; + } + } + } + } + false + } + pub fn default_binding(game_input: GameInput) -> KeyMouse { - // If a new GameInput is added, be sure to update ControlSettings::default() - // too! + // If a new GameInput is added, be sure to update GameInput::iterator() too! match game_input { GameInput::Primary => KeyMouse::Mouse(MouseButton::Left), GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right), @@ -127,6 +141,7 @@ impl ControlSettings { GameInput::Map => KeyMouse::Key(VirtualKeyCode::M), GameInput::Bag => KeyMouse::Key(VirtualKeyCode::B), GameInput::Social => KeyMouse::Key(VirtualKeyCode::O), + GameInput::Crafting => KeyMouse::Key(VirtualKeyCode::C), GameInput::Spellbook => KeyMouse::Key(VirtualKeyCode::P), GameInput::Settings => KeyMouse::Key(VirtualKeyCode::N), GameInput::Help => KeyMouse::Key(VirtualKeyCode::F1), @@ -157,6 +172,7 @@ impl ControlSettings { } } } + impl Default for ControlSettings { fn default() -> Self { let mut new_settings = Self { @@ -190,6 +206,7 @@ impl Default for ControlSettings { GameInput::Map, GameInput::Bag, GameInput::Social, + GameInput::Crafting, GameInput::Spellbook, GameInput::Settings, GameInput::ToggleInterface, @@ -294,6 +311,7 @@ pub mod con_settings { pub quest_log: Button, pub character_window: Button, pub social: Button, + pub crafting: Button, pub spellbook: Button, pub settings: Button, pub help: Button, @@ -383,6 +401,7 @@ pub mod con_settings { quest_log: Button::Simple(GilButton::Unknown), character_window: Button::Simple(GilButton::Unknown), social: Button::Simple(GilButton::Unknown), + crafting: Button::Simple(GilButton::Unknown), spellbook: Button::Simple(GilButton::Unknown), settings: Button::Simple(GilButton::Unknown), help: Button::Simple(GilButton::Unknown), @@ -480,6 +499,7 @@ pub struct GameplaySettings { pub auto_walk_behavior: PressBehavior, pub stop_auto_walk_on_input: bool, pub map_zoom: f64, + pub loading_tips: bool, } impl Default for GameplaySettings { @@ -503,12 +523,13 @@ impl Default for GameplaySettings { intro_show: Intro::Show, xp_bar: XpBar::Always, shortcut_numbers: ShortcutNumbers::On, - bar_numbers: BarNumbers::Off, + bar_numbers: BarNumbers::Values, ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()), free_look_behavior: PressBehavior::Toggle, auto_walk_behavior: PressBehavior::Toggle, stop_auto_walk_on_input: true, map_zoom: 4.0, + loading_tips: true, } } } diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index aee967caf4..ad67ed2ef3 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -1,7 +1,7 @@ use client::Client; use common::clock::Clock; -use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError}; -use server::{Event, Input, Server, ServerSettings}; +use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError}; +use server::{Error as ServerError, Event, Input, Server, ServerSettings}; use std::{ sync::{ atomic::{AtomicBool, Ordering}, @@ -23,6 +23,7 @@ enum Msg { pub struct Singleplayer { _server_thread: JoinHandle<()>, sender: Sender, + pub receiver: Receiver>, // Wether the server is stopped or not paused: Arc, } @@ -47,8 +48,19 @@ impl Singleplayer { let paused = Arc::new(AtomicBool::new(false)); let paused1 = paused.clone(); + let (result_sender, result_receiver) = bounded(1); + let thread = thread::spawn(move || { - let server = Server::new(settings2).expect("Failed to create server instance!"); + let server = match Server::new(settings2) { + Ok(server) => { + result_sender.send(Ok(())).unwrap(); + server + }, + Err(error) => { + result_sender.send(Err(error)).unwrap(); + return; + }, + }; let server = match thread_pool { Some(pool) => server.with_thread_pool(pool), @@ -62,6 +74,7 @@ impl Singleplayer { Singleplayer { _server_thread: thread, sender, + receiver: result_receiver, paused, }, settings, diff --git a/voxygen/src/ui/event.rs b/voxygen/src/ui/event.rs index b6a6dc20d7..e5a30b62cf 100644 --- a/voxygen/src/ui/event.rs +++ b/voxygen/src/ui/event.rs @@ -1,26 +1,30 @@ use conrod_core::{event::Input, input::Button}; use vek::*; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Event(pub Input); impl Event { pub fn try_from( - event: glutin::Event, - window: &glutin::ContextWrapper, + event: &winit::event::Event<()>, + window: &glutin::ContextWrapper, ) -> Option { use conrod_winit::*; // A wrapper around the winit window that allows us to implement the trait // necessary for enabling the winit <-> conrod conversion functions. - struct WindowRef<'a>(&'a winit::Window); + struct WindowRef<'a>(&'a winit::window::Window); // Implement the `WinitWindow` trait for `WindowRef` to allow for generating // compatible conversion functions. impl<'a> conrod_winit::WinitWindow for WindowRef<'a> { fn get_inner_size(&self) -> Option<(u32, u32)> { - winit::Window::get_inner_size(&self.0).map(Into::into) + Some( + winit::window::Window::inner_size(&self.0) + .to_logical::(self.hidpi_factor()) + .into(), + ) } - fn hidpi_factor(&self) -> f32 { winit::Window::get_hidpi_factor(&self.0) as _ } + fn hidpi_factor(&self) -> f64 { winit::window::Window::scale_factor(&self.0) } } convert_event!(event, &WindowRef(window.window())).map(Self) } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index a83adf8a16..57ee930a94 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -239,8 +239,10 @@ impl Ui { pub fn handle_event(&mut self, event: Event) { match event.0 { - Input::Resize(w, h) if w > 1.0 && h > 1.0 => { - self.window_resized = Some(Vec2::new(w, h)) + Input::Resize(w, h) => { + if w > 1.0 && h > 1.0 { + self.window_resized = Some(Vec2::new(w, h)) + } }, Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch { xy: self.scale.scale_point(touch.xy.into()).into_array(), @@ -459,7 +461,7 @@ impl Ui { || image_h == 0 || source_w < 1.0 || source_h < 1.0 - || gl_size.reduce_partial_max() < f32::EPSILON + || gl_size.reduce_partial_min() < f32::EPSILON { None } else { diff --git a/voxygen/src/ui/scale.rs b/voxygen/src/ui/scale.rs index e946d2c570..01eb21afba 100644 --- a/voxygen/src/ui/scale.rs +++ b/voxygen/src/ui/scale.rs @@ -19,7 +19,7 @@ pub enum ScaleMode { pub struct Scale { mode: ScaleMode, // Current dpi factor - dpi_factor: f64, + scale_factor: f64, // Current logical window size window_dims: Vec2, } @@ -27,10 +27,10 @@ pub struct Scale { impl Scale { pub fn new(window: &Window, mode: ScaleMode) -> Self { let window_dims = window.logical_size(); - let dpi_factor = window.renderer().get_resolution().x as f64 / window_dims.x; + let scale_factor = window.renderer().get_resolution().x as f64 / window_dims.x; Scale { mode, - dpi_factor, + scale_factor, window_dims, } } @@ -54,7 +54,7 @@ impl Scale { // coordinates. pub fn scale_factor_logical(&self) -> f64 { match self.mode { - ScaleMode::Absolute(scale) => scale / self.dpi_factor, + ScaleMode::Absolute(scale) => scale / self.scale_factor, ScaleMode::DpiFactor => 1.0, ScaleMode::RelativeToWindow(dims) => { (self.window_dims.x / dims.x).min(self.window_dims.y / dims.y) @@ -64,11 +64,11 @@ impl Scale { // Calculate factor to transform between physical coordinates and our scaled // coordinates. - pub fn scale_factor_physical(&self) -> f64 { self.scale_factor_logical() * self.dpi_factor } + pub fn scale_factor_physical(&self) -> f64 { self.scale_factor_logical() * self.scale_factor } - // Updates internal window size (and/or dpi_factor). + // Updates internal window size (and/or scale_factor). pub fn window_resized(&mut self, new_dims: Vec2, renderer: &Renderer) { - self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x; + self.scale_factor = renderer.get_resolution().x as f64 / new_dims.x; self.window_dims = new_dims; } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index ffcd9d9530..1c74601aec 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -4,10 +4,10 @@ use crate::{ settings::{ControlSettings, Settings}, ui, Error, }; +use crossbeam::channel; use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; - -use crossbeam::channel; +use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt}; use serde_derive::{Deserialize, Serialize}; use std::fmt; use tracing::{error, info, warn}; @@ -49,6 +49,7 @@ pub enum GameInput { Map, Bag, Social, + Crafting, Spellbook, Settings, ToggleInterface, @@ -95,6 +96,7 @@ impl GameInput { GameInput::Map => "gameinput.map", GameInput::Bag => "gameinput.bag", GameInput::Social => "gameinput.social", + GameInput::Crafting => "gameinput.crafting", GameInput::Spellbook => "gameinput.spellbook", GameInput::Settings => "gameinput.settings", GameInput::ToggleInterface => "gameinput.toggleinterface", @@ -123,6 +125,85 @@ impl GameInput { GameInput::SwapLoadout => "gameinput.swaploadout", } } + + pub fn iterator() -> impl Iterator { + [ + GameInput::Primary, + GameInput::Secondary, + GameInput::ToggleCursor, + GameInput::MoveForward, + GameInput::MoveLeft, + GameInput::MoveRight, + GameInput::MoveBack, + GameInput::Jump, + GameInput::Sit, + GameInput::Dance, + GameInput::Glide, + GameInput::Climb, + GameInput::ClimbDown, + GameInput::Swim, + GameInput::ToggleLantern, + GameInput::Mount, + GameInput::Enter, + GameInput::Command, + GameInput::Escape, + GameInput::Map, + GameInput::Bag, + GameInput::Social, + GameInput::Spellbook, + GameInput::Settings, + GameInput::ToggleInterface, + GameInput::Help, + GameInput::ToggleDebug, + GameInput::Fullscreen, + GameInput::Screenshot, + GameInput::ToggleIngameUi, + GameInput::Roll, + GameInput::Respawn, + GameInput::Interact, + GameInput::ToggleWield, + GameInput::FreeLook, + GameInput::AutoWalk, + GameInput::Slot1, + GameInput::Slot2, + GameInput::Slot3, + GameInput::Slot4, + GameInput::Slot5, + GameInput::Slot6, + GameInput::Slot7, + GameInput::Slot8, + GameInput::Slot9, + GameInput::Slot10, + GameInput::SwapLoadout, + ] + .iter() + .copied() + } + + /// Return true if `a` and `b` are able to be bound to the same key at the + /// same time without conflict. For example, the player can't jump and climb + /// at the same time, so these can be bound to the same key. + pub fn can_share_bindings(a: GameInput, b: GameInput) -> bool { + a.get_representative_binding() == b.get_representative_binding() + } + + /// If two GameInputs are able to be bound at the same time, then they will + /// return the same value from this function (the representative value for + /// that set). This models the Find operation of a disjoint-set data + /// structure. + fn get_representative_binding(&self) -> GameInput { + match self { + GameInput::Jump => GameInput::Jump, + GameInput::Climb => GameInput::Jump, + GameInput::Swim => GameInput::Jump, + GameInput::Respawn => GameInput::Jump, + + GameInput::FreeLook => GameInput::FreeLook, + GameInput::AutoWalk => GameInput::FreeLook, + + _ => *self, + } + } } /// Represents a key that the game menus recognise after input mapping @@ -160,7 +241,7 @@ pub enum AnalogGameInput { } /// Represents an incoming event from the window. -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Event { /// The window has been requested to close. Close, @@ -199,19 +280,20 @@ pub enum Event { ScreenshotMessage(String), } -pub type MouseButton = winit::MouseButton; -pub type PressState = winit::ElementState; +pub type MouseButton = winit::event::MouseButton; +pub type PressState = winit::event::ElementState; +pub type EventLoop = winit::event_loop::EventLoop<()>; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum KeyMouse { - Key(glutin::VirtualKeyCode), - Mouse(glutin::MouseButton), + Key(winit::event::VirtualKeyCode), + Mouse(winit::event::MouseButton), } impl fmt::Display for KeyMouse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::KeyMouse::*; - use glutin::{MouseButton, VirtualKeyCode::*}; + use winit::event::{MouseButton, VirtualKeyCode::*}; write!(f, "{}", match self { Key(Key1) => "1", Key(Key2) => "2", @@ -385,54 +467,59 @@ impl fmt::Display for KeyMouse { } pub struct Window { - events_loop: glutin::EventsLoop, renderer: Renderer, - window: glutin::ContextWrapper, + window: glutin::ContextWrapper, cursor_grabbed: bool, pub pan_sensitivity: u32, pub zoom_sensitivity: u32, pub zoom_inversion: bool, pub mouse_y_inversion: bool, fullscreen: bool, + modifiers: winit::event::ModifiersState, needs_refresh_resize: bool, - keypress_map: HashMap, + keypress_map: HashMap, pub remapping_keybindings: Option, - supplement_events: Vec, + events: Vec, focused: bool, gilrs: Option, controller_settings: ControllerSettings, - cursor_position: winit::dpi::LogicalPosition, + cursor_position: winit::dpi::PhysicalPosition, mouse_emulation_vec: Vec2, // Currently used to send and receive screenshot result messages message_sender: channel::Sender, message_receiver: channel::Receiver, + // Used for screenshots & fullscreen toggle to deduplicate/postpone to after event handler + take_screenshot: bool, + toggle_fullscreen: bool, } impl Window { - pub fn new(settings: &Settings) -> Result { - let events_loop = glutin::EventsLoop::new(); + pub fn new(settings: &Settings) -> Result<(Window, EventLoop), Error> { + let event_loop = EventLoop::new(); let size = settings.graphics.window_size; - let win_builder = glutin::WindowBuilder::new() + let win_builder = winit::window::WindowBuilder::new() .with_title("Veloren") - .with_dimensions(glutin::dpi::LogicalSize::new( - size[0] as f64, - size[1] as f64, - )) + .with_inner_size(winit::dpi::LogicalSize::new(size[0] as f64, size[1] as f64)) .with_maximized(true); - let ctx_builder = glutin::ContextBuilder::new() - .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 3))) - .with_vsync(false); + // Avoid cpal / winit OleInitialize conflict + // See: https://github.com/rust-windowing/winit/pull/1524 + #[cfg(target_os = "windows")] + let win_builder = winit::platform::windows::WindowBuilderExtWindows::with_drag_and_drop( + win_builder, + false, + ); let (window, device, factory, win_color_view, win_depth_view) = - gfx_window_glutin::init::( - win_builder, - ctx_builder, - &events_loop, - ) - .map_err(|err| Error::BackendError(Box::new(err)))?; + glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 3))) + .with_vsync(false) + .with_gfx_color_depth::() + .build_windowed(win_builder, &event_loop) + .map_err(|err| Error::BackendError(Box::new(err)))? + .init_gfx::(); let vendor = device.get_info().platform_name.vendor; let renderer = device.get_info().platform_name.renderer; @@ -478,7 +565,6 @@ impl Window { ) = channel::unbounded::(); let mut this = Self { - events_loop, renderer: Renderer::new( device, factory, @@ -493,211 +579,61 @@ impl Window { zoom_inversion: settings.gameplay.zoom_inversion, mouse_y_inversion: settings.gameplay.mouse_y_inversion, fullscreen: false, + modifiers: Default::default(), needs_refresh_resize: false, keypress_map, remapping_keybindings: None, - supplement_events: vec![], + events: Vec::new(), focused: true, gilrs, controller_settings, - cursor_position: winit::dpi::LogicalPosition::new(0.0, 0.0), + cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0), mouse_emulation_vec: Vec2::zero(), // Currently used to send and receive screenshot result messages message_sender, message_receiver, + take_screenshot: false, + toggle_fullscreen: false, }; this.fullscreen(settings.graphics.fullscreen); - Ok(this) + Ok((this, event_loop)) + } + + pub fn window( + &self, + ) -> &glutin::ContextWrapper { + &self.window } pub fn renderer(&self) -> &Renderer { &self.renderer } pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer } - #[allow(clippy::match_bool)] // TODO: Pending review in #587 - pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec { - let mut events = vec![]; - events.append(&mut self.supplement_events); + pub fn resolve_deduplicated_events(&mut self, settings: &mut Settings) { + // Handle screenshots and toggling fullscreen + if self.take_screenshot { + self.take_screenshot = false; + self.take_screenshot(&settings); + } + if self.toggle_fullscreen { + self.toggle_fullscreen = false; + self.toggle_fullscreen(settings); + } + } + + pub fn fetch_events(&mut self) -> Vec { // Refresh ui size (used when changing playstates) if self.needs_refresh_resize { - events.push(Event::Ui(ui::Event::new_resize(self.logical_size()))); + self.events + .push(Event::Ui(ui::Event::new_resize(self.logical_size()))); self.needs_refresh_resize = false; } // Receive any messages sent through the message channel - self.message_receiver - .try_iter() - .for_each(|message| events.push(Event::ScreenshotMessage(message))); - - // Copy data that is needed by the events closure to avoid lifetime errors. - // TODO: Remove this if/when the compiler permits it. - let cursor_grabbed = self.cursor_grabbed; - let renderer = &mut self.renderer; - let window = &mut self.window; - let remapping_keybindings = &mut self.remapping_keybindings; - let focused = &mut self.focused; - let controls = &mut settings.controls; - let keypress_map = &mut self.keypress_map; - let pan_sensitivity = self.pan_sensitivity; - let zoom_sensitivity = self.zoom_sensitivity; - let zoom_inversion = match self.zoom_inversion { - true => -1.0, - false => 1.0, - }; - let mouse_y_inversion = match self.mouse_y_inversion { - true => -1.0, - false => 1.0, - }; - let mut toggle_fullscreen = false; - let mut take_screenshot = false; - let mut cursor_position = None; - - self.events_loop.poll_events(|event| { - // Get events for ui. - if let Some(event) = ui::Event::try_from(event.clone(), window) { - events.push(Event::Ui(event)); - } - - match event { - glutin::Event::WindowEvent { event, .. } => match event { - glutin::WindowEvent::CloseRequested => events.push(Event::Close), - glutin::WindowEvent::Resized(glutin::dpi::LogicalSize { width, height }) => { - let (mut color_view, mut depth_view) = renderer.win_views_mut(); - gfx_window_glutin::update_views(window, &mut color_view, &mut depth_view); - renderer.on_resize().unwrap(); - events.push(Event::Resize(Vec2::new(width as u32, height as u32))); - }, - glutin::WindowEvent::Moved(glutin::dpi::LogicalPosition { x, y }) => { - events.push(Event::Moved(Vec2::new(x as u32, y as u32))) - }, - glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), - glutin::WindowEvent::MouseInput { button, state, .. } => { - if let (true, Some(game_inputs)) = ( - cursor_grabbed, - Window::map_input( - KeyMouse::Mouse(button), - controls, - remapping_keybindings, - ), - ) { - for game_input in game_inputs { - events.push(Event::InputUpdate( - *game_input, - state == glutin::ElementState::Pressed, - )); - } - } - events.push(Event::MouseButton(button, state)); - }, - glutin::WindowEvent::KeyboardInput { input, .. } => { - if let Some(key) = input.virtual_keycode { - if let Some(game_inputs) = Window::map_input( - KeyMouse::Key(key), - controls, - remapping_keybindings, - ) { - for game_input in game_inputs { - match game_input { - GameInput::Fullscreen => { - if input.state == glutin::ElementState::Pressed - && !Self::is_pressed( - keypress_map, - GameInput::Fullscreen, - ) - { - toggle_fullscreen = !toggle_fullscreen; - } - Self::set_pressed( - keypress_map, - GameInput::Fullscreen, - input.state, - ); - }, - GameInput::Screenshot => { - take_screenshot = input.state - == glutin::ElementState::Pressed - && !Self::is_pressed( - keypress_map, - GameInput::Screenshot, - ); - Self::set_pressed( - keypress_map, - GameInput::Screenshot, - input.state, - ); - }, - _ => events.push(Event::InputUpdate( - *game_input, - input.state == glutin::ElementState::Pressed, - )), - } - } - } - } - }, - - glutin::WindowEvent::Focused(state) => { - *focused = state; - events.push(Event::Focused(state)); - }, - glutin::WindowEvent::CursorMoved { position, .. } => { - cursor_position = Some(position); - }, - _ => {}, - }, - glutin::Event::DeviceEvent { event, .. } => match event { - glutin::DeviceEvent::MouseMotion { - delta: (dx, dy), .. - } if *focused => { - let delta = Vec2::new( - dx as f32 * (pan_sensitivity as f32 / 100.0), - dy as f32 * (pan_sensitivity as f32 * mouse_y_inversion / 100.0), - ); - - if cursor_grabbed { - events.push(Event::CursorPan(delta)); - } else { - events.push(Event::CursorMove(delta)); - } - }, - glutin::DeviceEvent::MouseWheel { delta, .. } if cursor_grabbed && *focused => { - events.push(Event::Zoom({ - // Since scrolling apparently acts different depending on platform - #[cfg(target_os = "windows")] - const PLATFORM_FACTOR: f32 = -4.0; - #[cfg(not(target_os = "windows"))] - const PLATFORM_FACTOR: f32 = 1.0; - - let y = match delta { - glutin::MouseScrollDelta::LineDelta(_x, y) => y, - // TODO: Check to see if there is a better way to find the "line - // height" than just hardcoding 16.0 pixels. Alternately we could - // get rid of this and have the user set zoom sensitivity, since - // it's unlikely people would expect a configuration file to work - // across operating systems. - glutin::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32, - }; - y * (zoom_sensitivity as f32 / 100.0) * zoom_inversion * PLATFORM_FACTOR - })) - }, - _ => {}, - }, - _ => {}, - } - }); - - if let Some(pos) = cursor_position { - self.cursor_position = pos; - } - - if take_screenshot { - self.take_screenshot(&settings); - } - - if toggle_fullscreen { - self.toggle_fullscreen(settings); + for message in self.message_receiver.try_iter() { + self.events.push(Event::ScreenshotMessage(message)) } if let Some(gilrs) = &mut self.gilrs { @@ -725,7 +661,7 @@ impl Window { | EventType::ButtonRepeated(button, code) => { handle_buttons( &self.controller_settings, - &mut events, + &mut self.events, &Button::from((button, code)), true, ); @@ -733,7 +669,7 @@ impl Window { EventType::ButtonReleased(button, code) => { handle_buttons( &self.controller_settings, - &mut events, + &mut self.events, &Button::from((button, code)), false, ); @@ -760,13 +696,14 @@ impl Window { }, EventType::AxisChanged(axis, value, code) => { - let value = match self + let value = if self .controller_settings .inverted_axes .contains(&Axis::from((axis, code))) { - true => value * -1.0, - false => value, + -value + } else { + value }; let value = self @@ -782,17 +719,17 @@ impl Window { for action in actions { match *action { AxisGameAction::MovementX => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::MovementX(value), )); }, AxisGameAction::MovementY => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::MovementY(value), )); }, AxisGameAction::CameraX => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::CameraX( value * self.controller_settings.pan_sensitivity @@ -802,7 +739,7 @@ impl Window { )); }, AxisGameAction::CameraY => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::CameraY( value * self.controller_settings.pan_sensitivity @@ -823,22 +760,22 @@ impl Window { for action in actions { match *action { AxisMenuAction::MoveX => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::MoveX(value), )); }, AxisMenuAction::MoveY => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::MoveY(value), )); }, AxisMenuAction::ScrollX => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::ScrollX(value), )); }, AxisMenuAction::ScrollY => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::ScrollY(value), )); }, @@ -853,6 +790,7 @@ impl Window { } } + let mut events = std::mem::take(&mut self.events); // Mouse emulation for the menus, to be removed when a proper menu navigation // system is available if !self.cursor_grabbed { @@ -869,46 +807,219 @@ impl Window { self.mouse_emulation_vec.y = d * -1.0; None }, - _ => { - let event = Event::AnalogMenuInput(input); - Some(event) - }, - }, - Event::MenuInput(input, state) => match input { - MenuInput::Apply => Some(match state { - true => Event::Ui(ui::Event(conrod_core::event::Input::Press( - conrod_core::input::Button::Mouse( - conrod_core::input::state::mouse::Button::Left, - ), - ))), - false => Event::Ui(ui::Event(conrod_core::event::Input::Release( - conrod_core::input::Button::Mouse( - conrod_core::input::state::mouse::Button::Left, - ), - ))), - }), - _ => Some(event), + input => Some(Event::AnalogMenuInput(input)), }, + Event::MenuInput(MenuInput::Apply, state) => Some(match state { + true => Event::Ui(ui::Event(conrod_core::event::Input::Press( + conrod_core::input::Button::Mouse( + conrod_core::input::state::mouse::Button::Left, + ), + ))), + false => Event::Ui(ui::Event(conrod_core::event::Input::Release( + conrod_core::input::Button::Mouse( + conrod_core::input::state::mouse::Button::Left, + ), + ))), + }), _ => Some(event), }) .collect(); + let sensitivity = self.controller_settings.mouse_emulation_sensitivity; - if self.mouse_emulation_vec != Vec2::zero() { - self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32) - .unwrap_or(()); - } + // TODO: make this independent of framerate + // TODO: consider multiplying by scale factor + self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32); } + events } + pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) { + use winit::event::DeviceEvent; + + let mouse_y_inversion = match self.mouse_y_inversion { + true => -1.0, + false => 1.0, + }; + + match event { + DeviceEvent::MouseMotion { + delta: (dx, dy), .. + } if self.focused => { + let delta = Vec2::new( + dx as f32 * (self.pan_sensitivity as f32 / 100.0), + dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0), + ); + + if self.cursor_grabbed { + self.events.push(Event::CursorPan(delta)); + } else { + self.events.push(Event::CursorMove(delta)); + } + }, + DeviceEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => { + self.events.push(Event::Zoom({ + // Since scrolling apparently acts different depending on platform + #[cfg(target_os = "windows")] + const PLATFORM_FACTOR: f32 = -4.0; + #[cfg(not(target_os = "windows"))] + const PLATFORM_FACTOR: f32 = 1.0; + + let y = match delta { + winit::event::MouseScrollDelta::LineDelta(_x, y) => y, + // TODO: Check to see if there is a better way to find the "line + // height" than just hardcoding 16.0 pixels. Alternately we could + // get rid of this and have the user set zoom sensitivity, since + // it's unlikely people would expect a configuration file to work + // across operating systems. + winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32, + }; + y * (self.zoom_sensitivity as f32 / 100.0) + * if self.zoom_inversion { -1.0 } else { 1.0 } + * PLATFORM_FACTOR + })) + }, + _ => {}, + } + } + + pub fn handle_window_event( + &mut self, + event: winit::event::WindowEvent, + settings: &mut Settings, + ) { + use winit::event::WindowEvent; + + let controls = &mut settings.controls; + + match event { + WindowEvent::CloseRequested => self.events.push(Event::Close), + WindowEvent::Resized(winit::dpi::PhysicalSize { width, height }) => { + let (mut color_view, mut depth_view) = self.renderer.win_views_mut(); + self.window.update_gfx(&mut color_view, &mut depth_view); + self.renderer.on_resize().unwrap(); + // TODO: update users of this event with the fact that it is now the physical + // size + self.events + .push(Event::Resize(Vec2::new(width as u32, height as u32))); + }, + WindowEvent::ReceivedCharacter(c) => self.events.push(Event::Char(c)), + WindowEvent::MouseInput { button, state, .. } => { + if let (true, Some(game_inputs)) = + // Mouse input not mapped to input if it is not grabbed + ( + self.cursor_grabbed, + Window::map_input( + KeyMouse::Mouse(button), + controls, + &mut self.remapping_keybindings, + ), + ) { + for game_input in game_inputs { + self.events.push(Event::InputUpdate( + *game_input, + state == winit::event::ElementState::Pressed, + )); + } + } + self.events.push(Event::MouseButton(button, state)); + }, + WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers, + WindowEvent::KeyboardInput { + input, + is_synthetic, + .. + } => { + // Ignore synthetic tab presses so that we don't get tabs when alt-tabbing back + // into the window + if matches!( + input.virtual_keycode, + Some(winit::event::VirtualKeyCode::Tab) + ) && is_synthetic + { + return; + } + // Ignore Alt-F4 so we don't try to do anything heavy like take a screenshot + // when the window is about to close + if matches!(input, winit::event::KeyboardInput { + state: winit::event::ElementState::Pressed, + virtual_keycode: Some(winit::event::VirtualKeyCode::F4), + .. + }) && self.modifiers.alt() + { + return; + } + + if let Some(key) = input.virtual_keycode { + if let Some(game_inputs) = Window::map_input( + KeyMouse::Key(key), + controls, + &mut self.remapping_keybindings, + ) { + for game_input in game_inputs { + match game_input { + GameInput::Fullscreen => { + if input.state == winit::event::ElementState::Pressed + && !Self::is_pressed( + &mut self.keypress_map, + GameInput::Fullscreen, + ) + { + self.toggle_fullscreen = !self.toggle_fullscreen; + } + Self::set_pressed( + &mut self.keypress_map, + GameInput::Fullscreen, + input.state, + ); + }, + GameInput::Screenshot => { + self.take_screenshot = input.state + == winit::event::ElementState::Pressed + && !Self::is_pressed( + &mut self.keypress_map, + GameInput::Screenshot, + ); + Self::set_pressed( + &mut self.keypress_map, + GameInput::Screenshot, + input.state, + ); + }, + _ => self.events.push(Event::InputUpdate( + *game_input, + input.state == winit::event::ElementState::Pressed, + )), + } + } + } + } + }, + WindowEvent::Focused(state) => { + self.focused = state; + self.events.push(Event::Focused(state)); + }, + WindowEvent::CursorMoved { position, .. } => { + self.cursor_position = position; + }, + _ => {}, + } + } + /// Moves cursor by an offset - pub fn offset_cursor(&self, d: Vec2) -> Result<(), String> { - self.window - .window() - .set_cursor_position(winit::dpi::LogicalPosition::new( - d.x as f64 + self.cursor_position.x, - d.y as f64 + self.cursor_position.y, - )) + pub fn offset_cursor(&self, d: Vec2) { + if d != Vec2::zero() { + if let Err(err) = + self.window + .window() + .set_cursor_position(winit::dpi::LogicalPosition::new( + d.x as f64 + self.cursor_position.x, + d.y as f64 + self.cursor_position.y, + )) + { + error!("Error setting cursor position: {:?}", err); + } + } } pub fn swap_buffers(&self) -> Result<(), Error> { @@ -921,8 +1032,8 @@ impl Window { pub fn grab_cursor(&mut self, grab: bool) { self.cursor_grabbed = grab; - self.window.window().hide_cursor(grab); - let _ = self.window.window().grab_cursor(grab); + self.window.window().set_cursor_visible(!grab); + let _ = self.window.window().set_cursor_grab(grab); } pub fn toggle_fullscreen(&mut self, settings: &mut Settings) { @@ -937,7 +1048,24 @@ impl Window { let window = self.window.window(); self.fullscreen = fullscreen; if fullscreen { - window.set_fullscreen(Some(window.get_current_monitor())); + window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( + window + .current_monitor() + .video_modes() + .filter(|mode| mode.bit_depth() >= 24 && mode.refresh_rate() >= 59) + .max_by_key(|mode| mode.size().width) + .unwrap_or_else(|| { + warn!( + "No video mode with a bit depth of at least 24 and a refresh rate of \ + at least 60Hz found" + ); + window + .current_monitor() + .video_modes() + .max_by_key(|mode| mode.size().width) + .expect("No video modes available!!") + }), + ))); } else { window.set_fullscreen(None); } @@ -950,8 +1078,8 @@ impl Window { let (w, h) = self .window .window() - .get_inner_size() - .unwrap_or(glutin::dpi::LogicalSize::new(0.0, 0.0)) + .inner_size() + .to_logical::(self.window.window().scale_factor()) .into(); Vec2::new(w, h) } @@ -965,7 +1093,7 @@ impl Window { )); } - pub fn send_supplement_event(&mut self, event: Event) { self.supplement_events.push(event) } + pub fn send_event(&mut self, event: Event) { self.events.push(event) } pub fn take_screenshot(&mut self, settings: &Settings) { match self.renderer.create_screenshot() { @@ -1003,15 +1131,20 @@ impl Window { } } - fn is_pressed(map: &mut HashMap, input: GameInput) -> bool { - *(map.entry(input).or_insert(glutin::ElementState::Released)) - == glutin::ElementState::Pressed + fn is_pressed( + map: &mut HashMap, + input: GameInput, + ) -> bool { + *(map + .entry(input) + .or_insert(winit::event::ElementState::Released)) + == winit::event::ElementState::Pressed } fn set_pressed( - map: &mut HashMap, + map: &mut HashMap, input: GameInput, - state: glutin::ElementState, + state: winit::event::ElementState, ) { map.insert(input, state); } @@ -1026,6 +1159,7 @@ impl Window { remapping: &mut Option, ) -> Option> { match *remapping { + // TODO: save settings Some(game_input) => { controls.modify_binding(game_input, key_mouse); *remapping = None; diff --git a/voxygen/tests/check_i18n_files.rs b/voxygen/tests/check_i18n_files.rs deleted file mode 100644 index 1fbbbd7b51..0000000000 --- a/voxygen/tests/check_i18n_files.rs +++ /dev/null @@ -1,251 +0,0 @@ -use git2::Repository; -use ron::de::from_bytes; -use std::{ - collections::{HashMap, HashSet}, - fs, - path::{Path, PathBuf}, -}; -use veloren_voxygen::i18n::VoxygenLocalization; - -/// List localization files as a PathBuf vector -fn i18n_files(i18n_dir: &Path) -> Vec { - fs::read_dir(i18n_dir) - .unwrap() - .map(|res| res.map(|e| e.path()).unwrap()) - .filter(|e| match e.extension() { - Some(ext) => ext == "ron", - None => false, - }) - .collect() -} - -#[derive(Debug, PartialEq)] -enum LocalizationState { - UpToDate, - NotFound, - Outdated, - Unknown, - Unused, -} - -#[derive(Debug)] -struct LocalizationEntryState { - pub key_line: Option, - pub chuck_line_range: Option<(usize, usize)>, - pub commit_id: Option, - pub state: LocalizationState, -} - -impl LocalizationEntryState { - pub fn new() -> LocalizationEntryState { - LocalizationEntryState { - key_line: None, - chuck_line_range: None, - commit_id: None, - state: LocalizationState::Unknown, - } - } -} - -/// Returns the Git blob associated with the given reference and path -#[allow(clippy::expect_fun_call)] // TODO: Pending review in #587 -fn read_file_from_path<'a>( - repo: &'a git2::Repository, - reference: &git2::Reference, - path: &std::path::Path, -) -> git2::Blob<'a> { - let tree = reference - .peel_to_tree() - .expect("Impossible to peel HEAD to a tree object"); - tree.get_path(path) - .expect(&format!( - "Impossible to find the file {:?} in reference {:?}", - path, - reference.name() - )) - .to_object(&repo) - .unwrap() - .peel_to_blob() - .expect("Impossible to fetch the Git object") -} - -fn generate_key_version<'a>( - repo: &'a git2::Repository, - localization: &VoxygenLocalization, - path: &std::path::Path, - file_blob: &git2::Blob, -) -> HashMap { - let mut keys: HashMap = localization - .string_map - .keys() - .map(|k| (k.to_owned(), LocalizationEntryState::new())) - .collect(); - let mut to_process: HashSet<&String> = localization.string_map.keys().map(|k| k).collect(); - // Find key start lines - for (line_nb, line) in std::str::from_utf8(file_blob.content()) - .expect("UTF-8 file") - .split('\n') - .enumerate() - { - let mut found_key = None; - - for key in to_process.iter() { - if line.contains(key.as_str()) { - found_key = Some(key.to_owned()); - break; - } - } - - if let Some(key) = found_key { - keys.get_mut(key).unwrap().key_line = Some(line_nb); - to_process.remove(&key); - }; - } - - // Find commit for each keys - repo.blame_file(path, None) - .expect("Impossible to generate the Git blame") - .iter() - .for_each(|e: git2::BlameHunk| { - for state in keys.values_mut() { - let line = state.key_line.unwrap(); - - if line >= e.final_start_line() && line < e.final_start_line() + e.lines_in_hunk() { - state.chuck_line_range = Some(( - e.final_start_line(), - e.final_start_line() + e.lines_in_hunk(), - )); - state.commit_id = match state.commit_id { - Some(existing_commit) => { - match repo.graph_descendant_of(e.final_commit_id(), existing_commit) { - Ok(true) => Some(e.final_commit_id()), - Ok(false) => Some(existing_commit), - Err(err) => panic!(err), - } - }, - None => Some(e.final_commit_id()), - }; - } - } - }); - - keys -} - -#[test] -#[ignore] -#[allow(clippy::expect_fun_call)] // TODO: Pending review in #587 -#[allow(clippy::extra_unused_lifetimes)] // TODO: Pending review in #587 -#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 -fn test_all_localizations<'a>() { - // Generate paths - let i18n_asset_path = Path::new("assets/voxygen/i18n/"); - let en_i18n_path = i18n_asset_path.join("en.ron"); - let root_dir = std::env::current_dir() - .map(|p| p.parent().expect("").to_owned()) - .unwrap(); - let i18n_path = root_dir.join(i18n_asset_path); - - if !root_dir.join(&en_i18n_path).is_file() { - panic!("Reference language file not found {:?}", &en_i18n_path) - } - - // Initialize Git objects - let repo = Repository::discover(&root_dir).expect(&format!( - "Failed to open the Git repository at {:?}", - &root_dir - )); - let head_ref = repo.head().expect("Impossible to get the HEAD reference"); - - // Read HEAD for the reference language file - let i18n_en_blob = read_file_from_path(&repo, &head_ref, &en_i18n_path); - let loc: VoxygenLocalization = - from_bytes(i18n_en_blob.content()).expect("Expect to parse the RON file"); - let i18n_references: HashMap = - generate_key_version(&repo, &loc, &en_i18n_path, &i18n_en_blob); - - // Compare to other reference files - let i18n_files = i18n_files(&i18n_path); - for file in i18n_files { - let relfile = file.strip_prefix(&root_dir).unwrap(); - let mut uptodate_entries = 0; - if relfile == en_i18n_path { - continue; - } - println!("{:?}", relfile); - - // Find the localization entry state - let current_blob = read_file_from_path(&repo, &head_ref, &relfile); - let current_loc: VoxygenLocalization = - from_bytes(current_blob.content()).expect("Expect to parse the RON file"); - let mut current_i18n = generate_key_version(&repo, ¤t_loc, &relfile, ¤t_blob); - for (ref_key, ref_state) in i18n_references.iter() { - match current_i18n.get_mut(ref_key) { - Some(state) => { - let commit_id = state.commit_id.unwrap(); - let ref_commit_id = ref_state.commit_id.unwrap(); - if commit_id != ref_commit_id - && !repo - .graph_descendant_of(commit_id, ref_commit_id) - .unwrap_or(false) - { - state.state = LocalizationState::Outdated; - } else { - state.state = LocalizationState::UpToDate; - } - }, - None => { - current_i18n.insert(ref_key.to_owned(), LocalizationEntryState { - key_line: None, - chuck_line_range: None, - commit_id: None, - state: LocalizationState::NotFound, - }); - }, - } - } - - let ref_keys: HashSet<&String> = i18n_references.keys().collect(); - for (_, state) in current_i18n - .iter_mut() - .filter(|&(k, _)| !ref_keys.contains(k)) - { - state.state = LocalizationState::Unused; - } - - // Display - println!( - "{:10} {:60}{:40} {:40}", - "State", - "Key name", - relfile.to_str().unwrap(), - en_i18n_path.to_str().unwrap() - ); - - let mut sorted_keys: Vec<&String> = current_i18n.keys().collect(); - sorted_keys.sort(); - for key in sorted_keys { - let state = current_i18n.get(key).unwrap(); - if state.state != LocalizationState::UpToDate { - println!( - "[{:9}] {:60}{:40} {:40}", - format!("{:?}", state.state), - key, - state - .commit_id - .map(|s| format!("{}", s)) - .unwrap_or("None".to_string()), - i18n_references - .get(key) - .map(|s| s.commit_id) - .flatten() - .map(|s| format!("{}", s)) - .unwrap_or("None".to_string()), - ); - } else { - uptodate_entries += 1; - } - } - println!("{} entries are up-to-date\n", uptodate_entries); - } -} diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index bdebd65e3a..75365d1d08 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -287,11 +287,14 @@ impl<'a> BlockGen<'a> { BlockKind::WhiteFlower, BlockKind::YellowFlower, BlockKind::Sunflower, - BlockKind::Mushroom, + BlockKind::Mushroom, //TODO: Better spawnrules BlockKind::LeafyPlant, BlockKind::Blueberry, BlockKind::LingonBerry, BlockKind::Fern, + /*BlockKind::Twigs, // TODO: Better spawnrules + *BlockKind::Stones, // TODO: Better spawnrules + *BlockKind::ShinyGem, // TODO: Better spawnrules */ ]; let grasses = [ BlockKind::LongGrass, diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index d89ead7e0c..b6a3e81d36 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -71,7 +71,6 @@ impl<'a, R: Rng> GenCtx<'a, R> { } impl Civs { - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 pub fn generate(seed: u32, sim: &mut WorldSim) -> Self { let mut this = Self::default(); let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); @@ -121,7 +120,7 @@ impl Civs { for site in this.sites.iter() { let radius = 48i32; - let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); + let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32); let flatten_radius = match &site.kind { SiteKind::Settlement => 10.0, @@ -170,11 +169,9 @@ impl Civs { let mut cnt = 0; for site in this.sites.iter() { cnt += 1; - let wpos = site - .center - .map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { - e * sz as i32 + sz as i32 / 2 - }); + let wpos = site.center.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { + e * sz as i32 + sz as i32 / 2 + }); let mut rng = ctx.reseed().rng; let world_site = match &site.kind { diff --git a/world/src/lib.rs b/world/src/lib.rs index b940ea3d7e..50dfc78b6a 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -64,7 +64,6 @@ impl World { pub fn sample_blocks(&self) -> BlockGen { BlockGen::new(ColumnGen::new(&self.sim)) } - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 pub fn generate_chunk( &self, @@ -74,7 +73,7 @@ impl World { ) -> Result<(TerrainChunk, ChunkSupplement), ()> { let mut sampler = self.sample_blocks(); - let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let chunk_wpos2d = chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); let grid_border = 4; let zcache_grid = Grid::populate_from( TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index c07aa0ef90..bd69fd384d 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -418,7 +418,6 @@ impl<'a> MapConfig<'a> { /// returns the approximate altitude at that column. When in doubt, try /// using `MapConfig::sample_wpos` for this. #[allow(clippy::if_same_then_else)] // TODO: Pending review in #587 - #[allow(clippy::useless_conversion)] // TODO: Pending review in #587 #[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587 #[allow(clippy::many_single_char_names)] pub fn generate( @@ -459,253 +458,249 @@ impl<'a> MapConfig<'a> { let chunk_size = TerrainChunkSize::RECT_SIZE.map(|e| e as f64); - (0..dimensions.y * dimensions.x) - .into_iter() - .for_each(|chunk_idx| { - let i = chunk_idx % dimensions.x as usize; - let j = chunk_idx / dimensions.x as usize; + (0..dimensions.y * dimensions.x).for_each(|chunk_idx| { + let i = chunk_idx % dimensions.x as usize; + let j = chunk_idx / dimensions.x as usize; - let wposf = focus_rect + Vec2::new(i as f64, j as f64) * scale; - let pos = wposf.map(|e: f64| e as i32); - let wposf = wposf * chunk_size; + let wposf = focus_rect + Vec2::new(i as f64, j as f64) * scale; + let pos = wposf.map(|e: f64| e as i32); + let wposf = wposf * chunk_size; - let chunk_idx = if pos.reduce_partial_min() >= 0 - && pos.x < WORLD_SIZE.x as i32 - && pos.y < WORLD_SIZE.y as i32 - { - Some(vec2_as_uniform_idx(pos)) - } else { - None - }; + let chunk_idx = if pos.reduce_partial_min() >= 0 + && pos.x < WORLD_SIZE.x as i32 + && pos.y < WORLD_SIZE.y as i32 + { + Some(vec2_as_uniform_idx(pos)) + } else { + None + }; - let MapSample { - rgb, - alt, - downhill_wpos, - .. - } = sample_pos(pos); + let MapSample { + rgb, + alt, + downhill_wpos, + .. + } = sample_pos(pos); - let alt = alt as f32; - let wposi = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); - let mut rgb = rgb.map(|e| e as f64 / 255.0); + let alt = alt as f32; + let wposi = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let mut rgb = rgb.map(|e| e as f64 / 255.0); - // Material properties: - // - // For each material in the scene, - // k_s = (RGB) specular reflection constant - let mut k_s = Rgb::new(1.0, 1.0, 1.0); - // k_d = (RGB) diffuse reflection constant - let mut k_d = rgb; - // k_a = (RGB) ambient reflection constant - let mut k_a = rgb; - // α = (per-material) shininess constant - let mut alpha = 4.0; // 4.0; + // Material properties: + // + // For each material in the scene, + // k_s = (RGB) specular reflection constant + let mut k_s = Rgb::new(1.0, 1.0, 1.0); + // k_d = (RGB) diffuse reflection constant + let mut k_d = rgb; + // k_a = (RGB) ambient reflection constant + let mut k_a = rgb; + // α = (per-material) shininess constant + let mut alpha = 4.0; // 4.0; - // Compute connections - let mut has_river = false; - // NOTE: consider replacing neighbors with local_cells, since it is more - // accurate (though I'm not sure if it can matter for these - // purposes). - chunk_idx - .map(|chunk_idx| neighbors(chunk_idx).chain(iter::once(chunk_idx))) - .into_iter() - .flatten() - .for_each(|neighbor_posi| { - let neighbor_pos = uniform_idx_as_vec2(neighbor_posi); - let neighbor_wpos = neighbor_pos.map(|e| e as f64) * chunk_size; - let MapSample { connections, .. } = sample_pos(neighbor_pos); - NEIGHBOR_DELTA - .iter() - .zip( - connections - .as_ref() - .map(|e| e.iter()) - .into_iter() - .flatten() - .into_iter(), - ) - .for_each(|(&delta, connection)| { - let connection = if let Some(connection) = connection { - connection + // Compute connections + let mut has_river = false; + // NOTE: consider replacing neighbors with local_cells, since it is more + // accurate (though I'm not sure if it can matter for these + // purposes). + chunk_idx + .map(|chunk_idx| neighbors(chunk_idx).chain(iter::once(chunk_idx))) + .into_iter() + .flatten() + .for_each(|neighbor_posi| { + let neighbor_pos = uniform_idx_as_vec2(neighbor_posi); + let neighbor_wpos = neighbor_pos.map(|e| e as f64) * chunk_size; + let MapSample { connections, .. } = sample_pos(neighbor_pos); + NEIGHBOR_DELTA + .iter() + .zip( + connections + .as_ref() + .map(|e| e.iter()) + .into_iter() + .flatten() + .into_iter(), + ) + .for_each(|(&delta, connection)| { + let connection = if let Some(connection) = connection { + connection + } else { + return; + }; + let downhill_wpos = neighbor_wpos + + Vec2::from(delta).map(|e: i32| e as f64) * chunk_size; + let coeffs = river_spline_coeffs( + neighbor_wpos, + connection.spline_derivative, + downhill_wpos, + ); + let (_t, _pt, dist) = if let Some((t, pt, dist)) = + quadratic_nearest_point(&coeffs, wposf) + { + (t, pt, dist) + } else { + let ndist = wposf.distance_squared(neighbor_wpos); + let ddist = wposf.distance_squared(downhill_wpos); + if ndist <= ddist { + (0.0, neighbor_wpos, ndist) } else { - return; - }; - let downhill_wpos = neighbor_wpos - + Vec2::from(delta).map(|e: i32| e as f64) * chunk_size; - let coeffs = river_spline_coeffs( - neighbor_wpos, - connection.spline_derivative, - downhill_wpos, - ); - let (_t, _pt, dist) = if let Some((t, pt, dist)) = - quadratic_nearest_point(&coeffs, wposf) - { - (t, pt, dist) - } else { - let ndist = wposf.distance_squared(neighbor_wpos); - let ddist = wposf.distance_squared(downhill_wpos); - if ndist <= ddist { - (0.0, neighbor_wpos, ndist) - } else { - (1.0, downhill_wpos, ddist) - } - }; - let connection_dist = (dist.sqrt() - - (connection.width as f64 * 0.5).max(1.0)) - .max(0.0); - if connection_dist == 0.0 { - match connection.kind { - ConnectionKind::River => { - has_river = true; - }, - } + (1.0, downhill_wpos, ddist) } - }); - }); + }; + let connection_dist = + (dist.sqrt() - (connection.width as f64 * 0.5).max(1.0)).max(0.0); + if connection_dist == 0.0 { + match connection.kind { + ConnectionKind::River => { + has_river = true; + }, + } + } + }); + }); - // Color in connectins. - let water_color_factor = 2.0; - let g_water = 32.0 * water_color_factor; - let b_water = 64.0 * water_color_factor; - if has_river { - let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8) - .map(|e| e as f64 / 255.0); - rgb = water_rgb; - k_s = Rgb::new(1.0, 1.0, 1.0); - k_d = water_rgb; - k_a = water_rgb; - alpha = 0.255; + // Color in connectins. + let water_color_factor = 2.0; + let g_water = 32.0 * water_color_factor; + let b_water = 64.0 * water_color_factor; + if has_river { + let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8) + .map(|e| e as f64 / 255.0); + rgb = water_rgb; + k_s = Rgb::new(1.0, 1.0, 1.0); + k_d = water_rgb; + k_a = water_rgb; + alpha = 0.255; + } + + let downhill_alt = sample_wpos(downhill_wpos); + let cross_pos = wposi + + ((downhill_wpos - wposi) + .map(|e| e as f32) + .rotated_z(f32::consts::FRAC_PI_2) + .map(|e| e as i32)); + let cross_alt = sample_wpos(cross_pos); + // Pointing downhill, forward + // (index--note that (0,0,1) is backward right-handed) + let forward_vec = Vec3::new( + (downhill_wpos.x - wposi.x) as f64, + ((downhill_alt - alt) * gain) as f64 * lgain, + (downhill_wpos.y - wposi.y) as f64, + ); + // Pointing 90 degrees left (in horizontal xy) of downhill, up + // (middle--note that (1,0,0), 90 degrees CCW backward, is right right-handed) + let up_vec = Vec3::new( + (cross_pos.x - wposi.x) as f64, + ((cross_alt - alt) * gain) as f64 * lgain, + (cross_pos.y - wposi.y) as f64, + ); + // let surface_normal = Vec3::new(lgain * (f.y * u.z - f.z * u.y), -(f.x * u.z - + // f.z * u.x), lgain * (f.x * u.y - f.y * u.x)).normalized(); + // Then cross points "to the right" (upwards) on a right-handed coordinate + // system. (right-handed coordinate system means (0, 0, 1.0) is + // "forward" into the screen). + let surface_normal = forward_vec.cross(up_vec).normalized(); + + // TODO: Figure out if we can reimplement debugging. + /* if is_debug { + let quad = + |x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1); + if river_kind.is_none() || humidity != 0.0 { + quads[quad(humidity)][quad(temperature)] += 1; } - - let downhill_alt = sample_wpos(downhill_wpos); - let cross_pos = wposi - + ((downhill_wpos - wposi) - .map(|e| e as f32) - .rotated_z(f32::consts::FRAC_PI_2) - .map(|e| e as i32)); - let cross_alt = sample_wpos(cross_pos); - // Pointing downhill, forward - // (index--note that (0,0,1) is backward right-handed) - let forward_vec = Vec3::new( - (downhill_wpos.x - wposi.x) as f64, - ((downhill_alt - alt) * gain) as f64 * lgain, - (downhill_wpos.y - wposi.y) as f64, - ); - // Pointing 90 degrees left (in horizontal xy) of downhill, up - // (middle--note that (1,0,0), 90 degrees CCW backward, is right right-handed) - let up_vec = Vec3::new( - (cross_pos.x - wposi.x) as f64, - ((cross_alt - alt) * gain) as f64 * lgain, - (cross_pos.y - wposi.y) as f64, - ); - // let surface_normal = Vec3::new(lgain * (f.y * u.z - f.z * u.y), -(f.x * u.z - - // f.z * u.x), lgain * (f.x * u.y - f.y * u.x)).normalized(); - // Then cross points "to the right" (upwards) on a right-handed coordinate - // system. (right-handed coordinate system means (0, 0, 1.0) is - // "forward" into the screen). - let surface_normal = forward_vec.cross(up_vec).normalized(); - - // TODO: Figure out if we can reimplement debugging. - /* if is_debug { - let quad = |x: f32| { - ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1) - }; - if river_kind.is_none() || humidity != 0.0 { - quads[quad(humidity)][quad(temperature)] += 1; - } - match river_kind { - Some(RiverKind::River { .. }) => { - rivers += 1; - }, - Some(RiverKind::Lake { .. }) => { - lakes += 1; - }, - Some(RiverKind::Ocean { .. }) => { - oceans += 1; - }, - None => {}, - } - } */ - - let shade_frac = horizon_map - .and_then(|(angles, heights)| { - chunk_idx - .and_then(|chunk_idx| angles.get(chunk_idx)) - .map(|&e| (e as f64, heights)) - }) - .and_then(|(e, heights)| { - chunk_idx - .and_then(|chunk_idx| heights.get(chunk_idx)) - .map(|&f| (e, f as f64)) - }) - .map(|(angle, height)| { - let w = 0.1; - let height = (height - alt as Alt * gain as Alt).max(0.0); - if angle != 0.0 && light_direction.x != 0.0 && height != 0.0 { - let deltax = height / angle; - let lighty = (light_direction.y / light_direction.x * deltax).abs(); - let deltay = lighty - height; - let s = (deltay / deltax / w).min(1.0).max(0.0); - // Smoothstep - s * s * (3.0 - 2.0 * s) - } else { - 1.0 - } - }) - .unwrap_or(1.0); - - let rgb = if is_shaded { - // Phong reflection model with shadows: - // - // I_p = k_a i_a + shadow * Σ {m ∈ lights} (k_d (L_m ⋅ N) i_m,d + k_s (R_m ⋅ - // V)^α i_m,s) - // - // where for the whole scene, - // i_a = (RGB) intensity of ambient lighting component - let i_a = Rgb::new(0.1, 0.1, 0.1); - // V = direction pointing towards the viewer (e.g. virtual camera). - let v = Vec3::new(0.0, 0.0, -1.0).normalized(); - // let v = Vec3::new(0.0, -1.0, 0.0).normalized(); - // - // for each light m, - // i_m,d = (RGB) intensity of diffuse component of light source m - let i_m_d = Rgb::new(1.0, 1.0, 1.0); - // i_m,s = (RGB) intensity of specular component of light source m - let i_m_s = Rgb::new(0.45, 0.45, 0.45); - // let i_m_s = Rgb::new(0.45, 0.45, 0.45); - - // for each light m and point p, - // L_m = (normalized) direction vector from point on surface to light source m - let l_m = light; - // N = (normalized) normal at this point on the surface, - let n = surface_normal; - // R_m = (normalized) direction a perfectly reflected ray of light from m would - // take from point p = 2(L_m ⋅ N)N - L_m - let r_m = (-l_m).reflected(n); // 2 * (l_m.dot(n)) * n - l_m; - // - // and for each point p in the scene, - // shadow = computed shadow factor at point p - // FIXME: Should really just be shade_frac, but with only ambient light we lose - // all local lighting detail... some sort of global illumination (e.g. - // radiosity) is of course the "right" solution, but maybe we can find - // something cheaper? - let shadow = 0.2 + 0.8 * shade_frac; - - let lambertian = l_m.dot(n).max(0.0); - let spec_angle = r_m.dot(v).max(0.0); - - let ambient = k_a * i_a; - let diffuse = k_d * lambertian * i_m_d; - let specular = k_s * spec_angle.powf(alpha) * i_m_s; - (ambient + shadow * (diffuse + specular)).map(|e| e.min(1.0)) - } else { - rgb + match river_kind { + Some(RiverKind::River { .. }) => { + rivers += 1; + }, + Some(RiverKind::Lake { .. }) => { + lakes += 1; + }, + Some(RiverKind::Ocean { .. }) => { + oceans += 1; + }, + None => {}, } - .map(|e| (e * 255.0) as u8); + } */ - let rgba = (rgb.r, rgb.g, rgb.b, 255); - write_pixel(Vec2::new(i, j), rgba); - }); + let shade_frac = horizon_map + .and_then(|(angles, heights)| { + chunk_idx + .and_then(|chunk_idx| angles.get(chunk_idx)) + .map(|&e| (e as f64, heights)) + }) + .and_then(|(e, heights)| { + chunk_idx + .and_then(|chunk_idx| heights.get(chunk_idx)) + .map(|&f| (e, f as f64)) + }) + .map(|(angle, height)| { + let w = 0.1; + let height = (height - alt as Alt * gain as Alt).max(0.0); + if angle != 0.0 && light_direction.x != 0.0 && height != 0.0 { + let deltax = height / angle; + let lighty = (light_direction.y / light_direction.x * deltax).abs(); + let deltay = lighty - height; + let s = (deltay / deltax / w).min(1.0).max(0.0); + // Smoothstep + s * s * (3.0 - 2.0 * s) + } else { + 1.0 + } + }) + .unwrap_or(1.0); + + let rgb = if is_shaded { + // Phong reflection model with shadows: + // + // I_p = k_a i_a + shadow * Σ {m ∈ lights} (k_d (L_m ⋅ N) i_m,d + k_s (R_m ⋅ + // V)^α i_m,s) + // + // where for the whole scene, + // i_a = (RGB) intensity of ambient lighting component + let i_a = Rgb::new(0.1, 0.1, 0.1); + // V = direction pointing towards the viewer (e.g. virtual camera). + let v = Vec3::new(0.0, 0.0, -1.0).normalized(); + // let v = Vec3::new(0.0, -1.0, 0.0).normalized(); + // + // for each light m, + // i_m,d = (RGB) intensity of diffuse component of light source m + let i_m_d = Rgb::new(1.0, 1.0, 1.0); + // i_m,s = (RGB) intensity of specular component of light source m + let i_m_s = Rgb::new(0.45, 0.45, 0.45); + // let i_m_s = Rgb::new(0.45, 0.45, 0.45); + + // for each light m and point p, + // L_m = (normalized) direction vector from point on surface to light source m + let l_m = light; + // N = (normalized) normal at this point on the surface, + let n = surface_normal; + // R_m = (normalized) direction a perfectly reflected ray of light from m would + // take from point p = 2(L_m ⋅ N)N - L_m + let r_m = (-l_m).reflected(n); // 2 * (l_m.dot(n)) * n - l_m; + // + // and for each point p in the scene, + // shadow = computed shadow factor at point p + // FIXME: Should really just be shade_frac, but with only ambient light we lose + // all local lighting detail... some sort of global illumination (e.g. + // radiosity) is of course the "right" solution, but maybe we can find + // something cheaper? + let shadow = 0.2 + 0.8 * shade_frac; + + let lambertian = l_m.dot(n).max(0.0); + let spec_angle = r_m.dot(v).max(0.0); + + let ambient = k_a * i_a; + let diffuse = k_d * lambertian * i_m_d; + let specular = k_s * spec_angle.powf(alpha) * i_m_s; + (ambient + shadow * (diffuse + specular)).map(|e| e.min(1.0)) + } else { + rgb + } + .map(|e| (e * 255.0) as u8); + + let rgba = (rgb.r, rgb.g, rgb.b, 255); + write_pixel(Vec2::new(i, j), rgba); + }); MapDebug { quads, diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 8f3124d705..34d6a0aebf 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -533,6 +533,9 @@ impl Floor { 11 => comp::Item::expect_from_asset( "common.items.weapons.sword.cultist_purp_2h-0", ), + 12 => comp::Item::expect_from_asset( + "common.items.armor.back.dungeon_purple-0", + ), _ => comp::Item::expect_from_asset( "common.items.boss_drops.exp_flask", ),