diff --git a/.gitignore b/.gitignore index dcd92a42c3..c57b217423 100644 --- a/.gitignore +++ b/.gitignore @@ -15,10 +15,7 @@ *.code-workspace # Veloren -voxygen/keybinds.toml -settings.toml -voxygen/settings/ *.rar *.log run.sh -screenshots \ No newline at end of file +screenshots diff --git a/Cargo.lock b/Cargo.lock index d113f9fc0e..2d72718ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,14 @@ dependencies = [ "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bincode" version = "1.1.4" @@ -349,21 +357,6 @@ name = "color_quant" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "config" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", - "serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "conrod_core" version = "0.63.0" @@ -1214,11 +1207,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "jpeg-decoder" version = "0.1.15" @@ -1304,15 +1292,6 @@ dependencies = [ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "linked-hash-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "linked-hash-map" version = "0.5.2" @@ -2133,9 +2112,14 @@ dependencies = [ ] [[package]] -name = "rust-ini" -version = "0.13.0" +name = "ron" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rustc-demangle" @@ -2174,11 +2158,6 @@ dependencies = [ "stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ryu" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "same-file" version = "1.0.4" @@ -2239,11 +2218,6 @@ name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "serde" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "serde" version = "1.0.91" @@ -2252,18 +2226,6 @@ dependencies = [ "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "serde-hjson" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "serde_derive" version = "1.0.91" @@ -2274,24 +2236,6 @@ dependencies = [ "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "serde_json" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_test" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "shared_library" version = "0.1.9" @@ -2539,14 +2483,6 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "toml" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "tuple_utils" version = "0.2.0" @@ -2669,7 +2605,6 @@ name = "veloren-voxygen" version = "0.2.0" dependencies = [ "backtrace 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git)", "conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git)", "directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2691,11 +2626,11 @@ dependencies = [ "portpicker 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "rodio 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", "veloren-client 0.2.0", "veloren-common 0.2.0", @@ -2903,14 +2838,6 @@ name = "xml-rs" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "yaml-rust" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [metadata] "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" @@ -2929,6 +2856,7 @@ dependencies = [ "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum backtrace 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "70af6de4789ac39587f100176ac7f704531e9e534b0f8676f658b3d909ce9a94" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7" "checksum bindgen 0.32.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b242e11a8f446f5fc7b76b37e81d737cabca562a927bd33766dac55b5f1177f" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" @@ -2954,7 +2882,6 @@ dependencies = [ "checksum cocoa 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0c23085dde1ef4429df6e5896b89356d35cdd321fb43afe3e378d010bb5adc6" "checksum cocoa 0.18.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf79daa4e11e5def06e55306aa3601b87de6b5149671529318da048f67cdd77b" "checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" -"checksum config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9107d78ed62b3fa5a86e7d18e647abed48cfd8f8fab6c72f4cdb982d196f7e6" "checksum conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "" "checksum conrod_derive 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "" "checksum conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "" @@ -3041,7 +2968,6 @@ dependencies = [ "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" "checksum instant 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d6706e8fb9de9be6143801a75747fa2209855b13d74ee994e30d86b38afdf77f" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c8b7d43206b34b3f94ea9445174bda196e772049b9bddbc620c9d29b2d20110d" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum khronos_api 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "037ab472c33f67b5fbd3e9163a2645319e5356fcd355efa6d4eb7fff4bbcb554" @@ -3054,7 +2980,6 @@ dependencies = [ "checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" -"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" @@ -3147,12 +3072,11 @@ dependencies = [ "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" "checksum rodio 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "10cb47941163cb747978d13a5c1b5c8fcd17f501817c4b77b9d69aed9ea240bc" -"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +"checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" "checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" "checksum rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rusttype 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "25951e85bb2647960969f72c559392245a5bd07446a589390bf427dda31cdc4a" -"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" "checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" "checksum scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b87497427f9fbe539ee6b9626f5a5e899331fdf1c1d62f14c637a462969db30" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" @@ -3161,12 +3085,8 @@ dependencies = [ "checksum sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c543ce8a6e33a30cb909612eeeb22e693848211a84558d5a00bb11e791b7ab7" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" "checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" -"checksum serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0b833c5ad67d52ced5f5938b2980f32a9c1c5ef047f0b4fb3127e7a423c76153" "checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" -"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" -"checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" "checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" "checksum shred 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6ea122e6133568144fcfb5888737d4ac776ebc959f989dd65b907136ac22bfed" "checksum shred-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fcf34e5e5302d3024aba7afc291f6d1ca7573ed035d3c0796976ba3f10691a1" @@ -3195,7 +3115,6 @@ dependencies = [ "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" "checksum tiff 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4834f28a0330cb9f3f2c87d2649dca723cb33802e2bdcf18da32759fbec7ce" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum tuple_utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbfecd7bb8f0a3e96b3b31c46af2677a55a588767c0091f484601424fcb20e7e" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" @@ -3226,4 +3145,3 @@ dependencies = [ "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" "checksum xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" "checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5" -"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index b8b0ef39f5..a080e6f9d6 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -1,4 +1,4 @@ -use client::{Client, Event, Input}; +use client::{Client, Event}; use common::{clock::Clock, comp}; use log::info; use std::time::Duration; @@ -23,7 +23,7 @@ fn main() { client.send_chat("Hello!".to_string()); loop { - let events = match client.tick(Input::default(), clock.get_last_delta()) { + let events = match client.tick(comp::Control::default(), clock.get_last_delta()) { Ok(events) => events, Err(err) => { println!("Error: {:?}", err); diff --git a/client/src/input.rs b/client/src/input.rs deleted file mode 100644 index a274697e29..0000000000 --- a/client/src/input.rs +++ /dev/null @@ -1,23 +0,0 @@ -use vek::*; - -pub enum InputEvent { - Jump, -} - -pub struct Input { - pub move_dir: Vec2, - pub jumping: bool, - pub gliding: bool, - pub events: Vec, -} - -impl Default for Input { - fn default() -> Self { - Input { - move_dir: Vec2::zero(), - jumping: false, - gliding: false, - events: Vec::new(), - } - } -} diff --git a/client/src/lib.rs b/client/src/lib.rs index be69fc5279..f8001fa101 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,13 +1,9 @@ #![feature(label_break_value, duration_float)] pub mod error; -pub mod input; // Reexports -pub use crate::{ - error::Error, - input::{Input, InputEvent}, -}; +pub use crate::error::Error; pub use specs::join::Join; pub use specs::Entity as EcsEntity; @@ -34,11 +30,11 @@ pub enum Event { } pub struct Client { - client_state: Option, + client_state: ClientState, thread_pool: ThreadPool, last_ping: f64, - pub postbox: PostBox, + postbox: PostBox, last_server_ping: Instant, last_ping_delta: f64, @@ -55,11 +51,11 @@ impl Client { /// Create a new `Client`. #[allow(dead_code)] pub fn new>(addr: A, view_distance: Option) -> Result { - let mut client_state = Some(ClientState::Connected); + let mut client_state = ClientState::Connected; let mut postbox = PostBox::to(addr)?; // Wait for initial sync - let (state, entity) = match postbox.next_message() { + let (mut state, entity) = match postbox.next_message() { Some(ServerMsg::InitialSync { ecs_state, entity_uid, @@ -95,8 +91,17 @@ impl Client { }) } + /// Request a state transition to `ClientState::Registered`. pub fn register(&mut self, player: comp::Player) { self.postbox.send_message(ClientMsg::Register { player }); + self.client_state = ClientState::Pending; + } + + /// Request a state transition to `ClientState::Character`. + pub fn request_character(&mut self, name: String, body: comp::Body) { + self.postbox + .send_message(ClientMsg::Character { name, body }); + self.client_state = ClientState::Pending; } pub fn set_view_distance(&mut self, view_distance: u32) { @@ -105,110 +110,101 @@ impl Client { .send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap())); // Can't fail } - /// Get a reference to the client's worker thread pool. This pool should be used for any - /// computationally expensive operations that run outside of the main thread (i.e., threads that - /// block on I/O operations are exempt). - #[allow(dead_code)] - pub fn thread_pool(&self) -> &threadpool::ThreadPool { - &self.thread_pool - } - - /// Get a reference to the client's game state. - #[allow(dead_code)] - pub fn state(&self) -> &State { - &self.state - } - - /// Get a mutable reference to the client's game state. - #[allow(dead_code)] - pub fn state_mut(&mut self) -> &mut State { - &mut self.state - } - - /// Get the player's entity. - #[allow(dead_code)] - pub fn entity(&self) -> EcsEntity { - self.entity - } - - /// Get the current tick number. - #[allow(dead_code)] - pub fn get_tick(&self) -> u64 { - self.tick - } - /// Send a chat message to the server. #[allow(dead_code)] pub fn send_chat(&mut self, msg: String) { self.postbox.send_message(ClientMsg::Chat(msg)) } + /// Jump locally, the new positions will be synced to the server #[allow(dead_code)] - pub fn get_ping_ms(&self) -> f64 { - self.last_ping_delta * 1000.0 + pub fn jump(&mut self) { + if self.client_state != ClientState::Character { + return; + } + self.state.write_component(self.entity, comp::Jumping); + } + + /// Start to glide locally, animation will be synced + #[allow(dead_code)] + pub fn glide(&mut self, state: bool) { + if self.client_state != ClientState::Character { + return; + } + if state { + self.state.write_component(self.entity, comp::Gliding); + } else { + self.state + .ecs_mut() + .write_storage::() + .remove(self.entity); + } + } + + /// Start to attack + #[allow(dead_code)] + pub fn attack(&mut self) { + if self.client_state != ClientState::Character { + return; + } + // TODO: Test if attack is possible using timeout + self.state + .write_component(self.entity, comp::Attacking::start()); + self.postbox.send_message(ClientMsg::Attack); + } + + /// Tell the server the client wants to respawn. + #[allow(dead_code)] + pub fn respawn(&mut self) { + if self.client_state != ClientState::Dead { + return; + } + self.postbox.send_message(ClientMsg::Respawn) + } + + /// Remove all cached terrain + #[allow(dead_code)] + pub fn clear_terrain(&mut self) { + self.state.clear_terrain(); + self.pending_chunks.clear(); } /// Execute a single client tick, handle input and update the game state by the given duration. #[allow(dead_code)] - pub fn tick(&mut self, input: Input, dt: Duration) -> Result, Error> { + pub fn tick(&mut self, control: comp::Control, dt: Duration) -> Result, Error> { // This tick function is the centre of the Veloren universe. Most client-side things are // managed from here, and as such it's important that it stays organised. Please consult // the core developers before making significant changes to this code. Here is the // approximate order of things. Please update it as this code changes. // // 1) Collect input from the frontend, apply input effects to the state of the game - // 2) Go through any events (timer-driven or otherwise) that need handling and apply them + // 2) Handle messages from the server + // 3) Go through any events (timer-driven or otherwise) that need handling and apply them // to the state of the game - // 3) Perform a single LocalState tick (i.e: update the world and entities in the world) - // 4) Go through the terrain update queue and apply all changes to the terrain - // 5) Finish the tick, passing control of the main thread back to the frontend + // 4) Perform a single LocalState tick (i.e: update the world and entities in the world) + // 5) Go through the terrain update queue and apply all changes to the terrain + // 6) Sync information to the server + // 7) Finish the tick, passing actions of the main thread back to the frontend - // Build up a list of events for this frame, to be passed to the frontend. + // 1) Handle input from frontend. + // Pass character actions from frontend input to the player's entity. + // TODO: Only do this if the entity already has a Inputs component! + if self.client_state == ClientState::Character { + self.state.write_component(self.entity, control.clone()); + } + + // 2) Build up a list of events for this frame, to be passed to the frontend. let mut frontend_events = Vec::new(); // Handle new messages from the server. frontend_events.append(&mut self.handle_new_messages()?); - // Pass character control from frontend input to the player's entity. - // TODO: Only do this if the entity already has a Control component! - self.state.write_component( - self.entity, - comp::Control { - move_dir: input.move_dir, - jumping: input.jumping, - gliding: input.gliding, - }, - ); + // 3) - // Tick the client's LocalState (step 3). + // 4) Tick the client's LocalState self.state.tick(dt); - // Update the server about the player's physics attributes. - match ( - self.state.read_storage().get(self.entity).cloned(), - self.state.read_storage().get(self.entity).cloned(), - self.state.read_storage().get(self.entity).cloned(), - ) { - (Some(pos), Some(vel), Some(dir)) => { - self.postbox - .send_message(ClientMsg::PlayerPhysics { pos, vel, dir }); - } - _ => {} - } - - // Update the server about the player's currently playing animation and the previous one. - if let Some(animation_history) = self - .state - .read_storage::() - .get(self.entity) - .cloned() - { - if Some(animation_history.current) != animation_history.last { - self.postbox - .send_message(ClientMsg::PlayerAnimation(animation_history)); - } - } - + // 5) Terrain let pos = self .state .read_storage::() @@ -259,13 +255,54 @@ impl Client { .retain(|_, created| now.duration_since(*created) < Duration::from_secs(10)); } - // send a ping to the server once every second + // Send a ping to the server once every second if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) { self.postbox.send_message(ClientMsg::Ping); self.last_server_ping = Instant::now(); } - // Finish the tick, pass control back to the frontend (step 6). + // 6) Update the server about the player's physics attributes. + match ( + self.state.read_storage().get(self.entity).cloned(), + self.state.read_storage().get(self.entity).cloned(), + self.state.read_storage().get(self.entity).cloned(), + ) { + (Some(pos), Some(vel), Some(dir)) => { + self.postbox + .send_message(ClientMsg::PlayerPhysics { pos, vel, dir }); + } + _ => {} + } + + // Update the server about the player's current animation. + if let Some(mut animation_info) = self + .state + .ecs_mut() + .write_storage::() + .get_mut(self.entity) + { + if animation_info.changed { + self.postbox + .send_message(ClientMsg::PlayerAnimation(animation_info.clone())); + } + } + + // 7) Finish the tick, pass control back to the frontend. + + // Cleanup + self.state + .ecs_mut() + .write_storage::() + .remove(self.entity); + self.state + .ecs_mut() + .write_storage::() + .remove(self.entity); + self.state + .ecs_mut() + .write_storage::() + .remove(self.entity); + self.tick += 1; Ok(frontend_events) } @@ -319,10 +356,10 @@ impl Client { }, ServerMsg::EntityAnimation { entity, - animation_history, + animation_info, } => match self.state.ecs().entity_from_uid(entity) { Some(entity) => { - self.state.write_component(entity, animation_history); + self.state.write_component(entity, animation_info); } None => {} }, @@ -331,16 +368,15 @@ impl Client { self.pending_chunks.remove(&key); } ServerMsg::StateAnswer(Ok(state)) => { - self.client_state = Some(state); + self.client_state = state; } ServerMsg::StateAnswer(Err((error, state))) => { - self.client_state = Some(state); + self.client_state = state; } ServerMsg::ForceState(state) => { - self.client_state = Some(state); + self.client_state = state; } ServerMsg::Disconnect => { - self.client_state = None; frontend_events.push(Event::Disconnect); } } @@ -357,6 +393,49 @@ impl Client { Ok(frontend_events) } + + /// Get the player's entity. + #[allow(dead_code)] + pub fn entity(&self) -> EcsEntity { + self.entity + } + + /// Get the client state + #[allow(dead_code)] + pub fn get_client_state(&self) -> ClientState { + self.client_state + } + + /// Get the current tick number. + #[allow(dead_code)] + pub fn get_tick(&self) -> u64 { + self.tick + } + + #[allow(dead_code)] + pub fn get_ping_ms(&self) -> f64 { + self.last_ping_delta * 1000.0 + } + + /// Get a reference to the client's worker thread pool. This pool should be used for any + /// computationally expensive operations that run outside of the main thread (i.e., threads that + /// block on I/O operations are exempt). + #[allow(dead_code)] + pub fn thread_pool(&self) -> &threadpool::ThreadPool { + &self.thread_pool + } + + /// Get a reference to the client's game state. + #[allow(dead_code)] + pub fn state(&self) -> &State { + &self.state + } + + /// Get a mutable reference to the client's game state. + #[allow(dead_code)] + pub fn state_mut(&mut self) -> &mut State { + &mut self.state + } } impl Drop for Client { diff --git a/common/src/comp/actor.rs b/common/src/comp/actor.rs index f17a9f9262..60cf6941e7 100644 --- a/common/src/comp/actor.rs +++ b/common/src/comp/actor.rs @@ -229,33 +229,3 @@ pub enum Actor { impl Component for Actor { type Storage = FlaggedStorage>; } - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct AnimationHistory { - pub last: Option, - pub current: Animation, - pub time: f64, -} - -impl AnimationHistory { - pub fn new(animation: Animation) -> Self { - Self { - last: None, - current: animation, - time: 0.0, - } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum Animation { - Idle, - Run, - Jump, - Gliding, - Attack, -} - -impl Component for AnimationHistory { - type Storage = FlaggedStorage>; -} diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 1159316afc..37a9faf6e6 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -13,24 +13,3 @@ pub enum Agent { impl Component for Agent { type Storage = VecStorage; } - -#[derive(Copy, Clone, Debug)] -pub struct Control { - pub move_dir: Vec2, - pub jumping: bool, - pub gliding: bool, -} - -impl Default for Control { - fn default() -> Self { - Self { - move_dir: Vec2::zero(), - jumping: false, - gliding: false, - } - } -} - -impl Component for Control { - type Storage = VecStorage; -} diff --git a/common/src/comp/animation.rs b/common/src/comp/animation.rs new file mode 100644 index 0000000000..c31761424d --- /dev/null +++ b/common/src/comp/animation.rs @@ -0,0 +1,32 @@ +use specs::{Component, FlaggedStorage, VecStorage}; +use vek::*; + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Animation { + Idle, + Run, + Jump, + Gliding, + Attack, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct AnimationInfo { + pub animation: Animation, + pub time: f64, + pub changed: bool, +} + +impl Default for AnimationInfo { + fn default() -> Self { + Self { + animation: Animation::Idle, + time: 0.0, + changed: true, + } + } +} + +impl Component for AnimationInfo { + type Storage = FlaggedStorage>; +} diff --git a/common/src/comp/inputs.rs b/common/src/comp/inputs.rs new file mode 100644 index 0000000000..ce55110ce0 --- /dev/null +++ b/common/src/comp/inputs.rs @@ -0,0 +1,49 @@ +use specs::{Component, FlaggedStorage, NullStorage, VecStorage}; +use vek::*; + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Control { + pub move_dir: Vec2, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Respawning; + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Attacking { + pub time: f32, + pub applied: bool, +} +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Jumping; + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Gliding; + +impl Component for Control { + type Storage = VecStorage; +} + +impl Component for Respawning { + type Storage = NullStorage; +} + +impl Attacking { + pub fn start() -> Self { + Self { + time: 0.0, + applied: false, + } + } +} +impl Component for Attacking { + type Storage = FlaggedStorage>; +} + +impl Component for Jumping { + type Storage = NullStorage; +} + +impl Component for Gliding { + type Storage = NullStorage; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index a75e09c083..03fb57f617 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,16 +1,24 @@ pub mod actor; pub mod agent; +pub mod animation; +pub mod inputs; pub mod phys; pub mod player; pub mod stats; // Reexports pub use actor::Actor; -pub use actor::Animation; -pub use actor::AnimationHistory; pub use actor::Body; pub use actor::HumanoidBody; pub use actor::QuadrupedBody; -pub use agent::{Agent, Control}; +pub use agent::Agent; +pub use animation::Animation; +pub use animation::AnimationInfo; +pub use inputs::Attacking; +pub use inputs::Control; +pub use inputs::Gliding; +pub use inputs::Jumping; +pub use inputs::Respawning; pub use player::Player; +pub use stats::Dying; pub use stats::Stats; diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index cb9765b6c1..8bfd9150bc 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -28,7 +28,7 @@ impl Component for Dir { type Storage = VecStorage; } -// Update +// ForceUpdate #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] pub struct ForceUpdate; diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 2c895083e0..68818560e4 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -1,4 +1,4 @@ -use specs::{Component, FlaggedStorage, VecStorage}; +use specs::{Component, FlaggedStorage, NullStorage, VecStorage}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Player { @@ -18,3 +18,9 @@ impl Player { impl Component for Player { type Storage = FlaggedStorage>; } + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Respawn; +impl Component for Respawn { + type Storage = NullStorage; +} diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 3b6e263af2..f5a7494bb2 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -1,6 +1,7 @@ -use specs::{Component, FlaggedStorage, VecStorage}; +use crate::state::Time; +use specs::{Component, FlaggedStorage, NullStorage, VecStorage}; -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Health { pub current: u32, pub maximum: u32, @@ -8,18 +9,24 @@ pub struct Health { } impl Health { - pub fn change_by(&mut self, amount: i32, current_time: f64) { + pub fn change_by(&mut self, amount: i32) { self.current = (self.current as i32 + amount).max(0) as u32; - self.last_change = Some((amount, current_time)); + self.last_change = Some((amount, 0.0)); } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Stats { pub hp: Health, pub xp: u32, } +impl Stats { + pub fn is_dead(&self) -> bool { + self.hp.current == 0 + } +} + impl Default for Stats { fn default() -> Self { Self { @@ -36,3 +43,10 @@ impl Default for Stats { impl Component for Stats { type Storage = FlaggedStorage>; } + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] +pub struct Dying; + +impl Component for Dying { + type Storage = NullStorage; +} diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 2c3d2bbc3f..b340b1797b 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -11,12 +11,14 @@ pub enum ClientMsg { name: String, body: comp::Body, }, + Attack, + Respawn, RequestState(ClientState), SetViewDistance(u32), Ping, Pong, Chat(String), - PlayerAnimation(comp::AnimationHistory), + PlayerAnimation(comp::AnimationInfo), PlayerPhysics { pos: comp::phys::Pos, vel: comp::phys::Vel, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 32af121dc4..c4e3163d62 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -23,6 +23,7 @@ sphynx::sum_type! { Actor(comp::Actor), Player(comp::Player), Stats(comp::Stats), + Attacking(comp::Attacking), } } // Automatically derive From for EcsCompPhantom @@ -36,6 +37,7 @@ sphynx::sum_type! { Actor(PhantomData), Player(PhantomData), Stats(PhantomData), + Attacking(PhantomData), } } impl sphynx::CompPacket for EcsCompPacket { diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index a5a94e89a3..8489bb5f0c 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -9,8 +9,10 @@ pub use self::server::{RequestStateError, ServerMsg}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ClientState { + Pending, Connected, Registered, Spectator, + Dead, Character, } diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 4a315dc5b7..379f65cddf 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -31,7 +31,7 @@ pub enum ServerMsg { }, EntityAnimation { entity: u64, - animation_history: comp::AnimationHistory, + animation_info: comp::AnimationInfo, }, TerrainChunkUpdate { key: Vec2, diff --git a/common/src/state.rs b/common/src/state.rs index ca5b3d87f4..61278edca3 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -28,8 +28,8 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0; pub struct TimeOfDay(f64); /// A resource that stores the tick (i.e: physics) time. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Time(f64); +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] +pub struct Time(pub f64); /// A resource that stores the time since the previous tick. #[derive(Default)] @@ -103,14 +103,21 @@ impl State { ecs.register_synced::(); ecs.register_synced::(); ecs.register_synced::(); + ecs.register_synced::(); + ecs.register::(); // Register unsynced (or synced by other means) components. ecs.register::(); ecs.register::(); ecs.register::(); - ecs.register::(); - ecs.register::(); + ecs.register::(); + ecs.register::(); ecs.register::(); + ecs.register::(); + ecs.register::(); + ecs.register::(); + ecs.register::(); + ecs.register::(); ecs.register::(); // Register synced resources used by the ECS. @@ -181,6 +188,24 @@ impl State { self.ecs.read_resource::() } + /// Get a writable reference to this state's terrain. + pub fn terrain_mut(&self) -> FetchMut { + self.ecs.write_resource::() + } + + /// Removes every chunk of the terrain. + pub fn clear_terrain(&mut self) { + let keys = self + .terrain_mut() + .drain() + .map(|(key, _)| key) + .collect::>(); + + for key in keys { + self.remove_chunk(key); + } + } + /// Insert the provided chunk into this state's terrain. pub fn insert_chunk(&mut self, key: Vec2, chunk: TerrainChunk) { if self diff --git a/common/src/sys/actions.rs b/common/src/sys/actions.rs new file mode 100644 index 0000000000..05613761f4 --- /dev/null +++ b/common/src/sys/actions.rs @@ -0,0 +1,43 @@ +// Library +use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; +use vek::*; + +// Crate +use crate::{ + comp::{ + phys::{Dir, Pos, Vel}, + Animation, AnimationInfo, Attacking, + }, + state::DeltaTime, + terrain::TerrainMap, + vol::{ReadVol, Vox}, +}; + +// Basic ECS AI agent system +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = ( + Entities<'a>, + Read<'a, DeltaTime>, + WriteStorage<'a, Attacking>, + ); + + fn run(&mut self, (entities, dt, mut attackings): Self::SystemData) { + for (entity, attacking) in (&entities, &mut attackings).join() { + attacking.time += dt.0; + } + + let finished_attack = (&entities, &mut attackings) + .join() + .filter(|(e, a)| { + a.time > 0.5 // TODO: constant + }) + .map(|(e, a)| e) + .collect::>(); + + for entity in finished_attack { + attackings.remove(entity); + } + } +} diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 87a45cd2c8..ea6bc8c320 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,22 +1,29 @@ // Library -use specs::{Join, Read, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use vek::*; // Crate -use crate::comp::{phys::Pos, Agent, Control}; +use crate::comp::{phys::Pos, Agent, Control, Jumping}; // Basic ECS AI agent system pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( + Entities<'a>, WriteStorage<'a, Agent>, ReadStorage<'a, Pos>, WriteStorage<'a, Control>, + WriteStorage<'a, Jumping>, ); - fn run(&mut self, (mut agents, positions, mut controls): Self::SystemData) { - for (mut agent, pos, mut control) in (&mut agents, &positions, &mut controls).join() { + fn run( + &mut self, + (entities, mut agents, positions, mut controls, mut jumpings): Self::SystemData, + ) { + for (entity, agent, pos, control) in + (&entities, &mut agents, &positions, &mut controls).join() + { match agent { Agent::Wanderer(bearing) => { *bearing += Vec2::new(rand::random::() - 0.5, rand::random::() - 0.5) @@ -34,8 +41,9 @@ impl<'a> System<'a> for Sys { Some(tgt_pos) => { let tgt_pos = tgt_pos.0 + *offset; - // Jump with target. - control.jumping = tgt_pos.z > pos.0.z + 1.0; + if tgt_pos.z > pos.0.z + 1.0 { + jumpings.insert(entity, Jumping); + } // Move towards the target. let dist = tgt_pos.distance(pos.0); diff --git a/common/src/sys/anim.rs b/common/src/sys/anim.rs deleted file mode 100644 index 569fa5a459..0000000000 --- a/common/src/sys/anim.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Library -use specs::{Join, Read, ReadStorage, System, WriteStorage}; -use vek::*; - -// Crate -use crate::{ - comp::{phys::Pos, AnimationHistory, Control}, - state::DeltaTime, -}; - -// Basic ECS AI agent system -pub struct Sys; - -impl<'a> System<'a> for Sys { - type SystemData = (Read<'a, DeltaTime>, WriteStorage<'a, AnimationHistory>); - - fn run(&mut self, (dt, mut anim_history): Self::SystemData) { - for (mut anim_history) in (&mut anim_history).join() { - anim_history.time += dt.0 as f64; - } - } -} diff --git a/common/src/sys/animation.rs b/common/src/sys/animation.rs new file mode 100644 index 0000000000..711e70f4be --- /dev/null +++ b/common/src/sys/animation.rs @@ -0,0 +1,22 @@ +// Library +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use vek::*; + +// Crate +use crate::{ + comp::{phys::Pos, Animation, AnimationInfo, Stats}, + state::DeltaTime, +}; + +// Basic ECS AI agent system +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = (Read<'a, DeltaTime>, WriteStorage<'a, AnimationInfo>); + + fn run(&mut self, (dt, mut animation_infos): Self::SystemData) { + for (mut animation_info) in (&mut animation_infos).join() { + animation_info.time += dt.0 as f64; + } + } +} diff --git a/common/src/sys/control.rs b/common/src/sys/control.rs deleted file mode 100644 index 0fa60fe0d0..0000000000 --- a/common/src/sys/control.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Library -use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; -use vek::*; - -// Crate -use crate::{ - comp::{ - phys::{Dir, Pos, Vel}, - Animation, AnimationHistory, Control, - }, - state::DeltaTime, - terrain::TerrainMap, - vol::{ReadVol, Vox}, -}; - -// Basic ECS AI agent system -pub struct Sys; - -impl<'a> System<'a> for Sys { - type SystemData = ( - ReadExpect<'a, TerrainMap>, - Read<'a, DeltaTime>, - Entities<'a>, - ReadStorage<'a, Pos>, - WriteStorage<'a, Vel>, - WriteStorage<'a, Dir>, - WriteStorage<'a, AnimationHistory>, - ReadStorage<'a, Control>, - ); - - fn run( - &mut self, - (terrain, dt, entities, pos, mut vels, mut dirs, mut anims, controls): Self::SystemData, - ) { - for (entity, pos, mut vel, mut dir, control) in - (&entities, &pos, &mut vels, &mut dirs, &controls).join() - { - let on_ground = terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|vox| !vox.is_empty()) - .unwrap_or(false) - && vel.0.z <= 0.0; - - let (gliding, friction) = if on_ground { - // TODO: Don't hard-code this. - // Apply physics to the player: acceleration and non-linear deceleration. - vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 200.0; - - if control.jumping { - vel.0.z += 16.0; - } - - (false, 0.15) - } else { - // TODO: Don't hard-code this. - // Apply physics to the player: acceleration and non-linear deceleration. - vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 10.0; - - if control.gliding && vel.0.z < 0.0 { - // TODO: Don't hard-code this. - let anti_grav = 9.81 * 3.95 + vel.0.z.powf(2.0) * 0.2; - vel.0.z += - dt.0 * anti_grav * Vec2::::from(vel.0 * 0.15).magnitude().min(1.0); - - (true, 0.008) - } else { - (false, 0.015) - } - }; - - // Friction - vel.0 -= Vec2::broadcast(dt.0) - * 50.0 - * vel.0.map(|e| { - (e.abs() * friction * (vel.0.magnitude() * 0.1 + 0.5)) - .min(e.abs() * dt.0 * 50.0) - .copysign(e) - }) - * Vec3::new(1.0, 1.0, 0.0); - - if vel.0.magnitude_squared() != 0.0 { - dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); - } - - let animation = if on_ground { - if control.move_dir.magnitude() > 0.01 { - Animation::Run - } else { - Animation::Idle - } - } else if gliding { - Animation::Gliding - } else { - Animation::Jump - }; - - let last_history = anims.get_mut(entity).cloned(); - - let time = if let Some((true, time)) = - last_history.map(|last| (last.current == animation, last.time)) - { - time - } else { - 0.0 - }; - - anims.insert( - entity, - AnimationHistory { - last: last_history.map(|last| last.current), - current: animation, - time, - }, - ); - } - } -} diff --git a/common/src/sys/inputs.rs b/common/src/sys/inputs.rs new file mode 100644 index 0000000000..1f3c74abb3 --- /dev/null +++ b/common/src/sys/inputs.rs @@ -0,0 +1,167 @@ +// Library +use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; +use vek::*; + +// Crate +use crate::{ + comp::{ + phys::{Dir, ForceUpdate, Pos, Vel}, + Animation, AnimationInfo, Attacking, Control, Gliding, Jumping, Respawning, Stats, + }, + state::{DeltaTime, Time}, + terrain::TerrainMap, + vol::{ReadVol, Vox}, +}; + +// Basic ECS AI agent system +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = ( + Entities<'a>, + Read<'a, Time>, + Read<'a, DeltaTime>, + ReadExpect<'a, TerrainMap>, + ReadStorage<'a, Pos>, + WriteStorage<'a, Vel>, + WriteStorage<'a, Dir>, + WriteStorage<'a, AnimationInfo>, + WriteStorage<'a, Stats>, + ReadStorage<'a, Control>, + ReadStorage<'a, Jumping>, + WriteStorage<'a, Respawning>, + WriteStorage<'a, Gliding>, + WriteStorage<'a, Attacking>, + WriteStorage<'a, ForceUpdate>, + ); + + fn run( + &mut self, + ( + entities, + time, + dt, + terrain, + positions, + mut velocities, + mut directions, + mut animation_infos, + mut stats, + mut controls, + mut jumpings, + mut respawnings, + mut glidings, + mut attackings, + mut force_updates, + ): Self::SystemData, + ) { + for (entity, pos, control, mut dir, mut vel) in ( + &entities, + &positions, + &controls, + &mut directions, + &mut velocities, + ) + .join() + { + // Handle held-down control + let on_ground = terrain + .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) + .map(|vox| !vox.is_empty()) + .unwrap_or(false) + && vel.0.z <= 0.0; + + let (gliding, friction) = if on_ground { + // TODO: Don't hard-code this. + // Apply physics to the player: acceleration and non-linear deceleration. + vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 200.0; + + if jumpings.get(entity).is_some() { + vel.0.z += 16.0; + } + + (false, 0.15) + } else { + // TODO: Don't hard-code this. + // Apply physics to the player: acceleration and non-linear deceleration. + vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 10.0; + + if glidings.get(entity).is_some() && vel.0.z < 0.0 { + // TODO: Don't hard-code this. + let anti_grav = 9.81 * 3.95 + vel.0.z.powf(2.0) * 0.2; + vel.0.z += + dt.0 * anti_grav * Vec2::::from(vel.0 * 0.15).magnitude().min(1.0); + + (true, 0.008) + } else { + (false, 0.015) + } + }; + + // Friction + vel.0 -= Vec2::broadcast(dt.0) + * 50.0 + * vel.0.map(|e| { + (e.abs() * friction * (vel.0.magnitude() * 0.1 + 0.5)) + .min(e.abs() * dt.0 * 50.0) + .copysign(e) + }) + * Vec3::new(1.0, 1.0, 0.0); + + if vel.0.magnitude_squared() != 0.0 { + dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0); + } + + let animation = if on_ground { + if control.move_dir.magnitude() > 0.01 { + Animation::Run + } else { + Animation::Idle + } + } else if glidings.get(entity).is_some() { + Animation::Gliding + } else { + Animation::Jump + }; + + let last = animation_infos + .get_mut(entity) + .cloned() + .unwrap_or(AnimationInfo::default()); + let changed = last.animation != animation; + + animation_infos.insert( + entity, + AnimationInfo { + animation, + time: if changed { 0.0 } else { last.time }, + changed, + }, + ); + } + + for (entity, pos, dir, attacking) in + (&entities, &positions, &directions, &mut attackings).join() + { + if !attacking.applied { + for (b, pos_b, mut stat_b, mut vel_b) in + (&entities, &positions, &mut stats, &mut velocities).join() + { + // Check if it is a hit + if entity != b + && !stat_b.is_dead() + && pos.0.distance_squared(pos_b.0) < 50.0 + && dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0 + { + // Deal damage + stat_b.hp.change_by(-10); // TODO: variable damage + vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0; + vel_b.0.z = 15.0; + force_updates.insert(b, ForceUpdate); + } + } + attacking.applied = true; + } + } + } +} diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index 85427a1e59..da6f62958b 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -1,20 +1,26 @@ +pub mod actions; pub mod agent; -pub mod anim; -pub mod control; +pub mod animation; +pub mod inputs; pub mod phys; +mod stats; // External use specs::DispatcherBuilder; // System names const AGENT_SYS: &str = "agent_sys"; -const CONTROL_SYS: &str = "control_sys"; +const INPUTS_SYS: &str = "inputs_sys"; +const ACTIONS_SYS: &str = "actions_sys"; const PHYS_SYS: &str = "phys_sys"; -const ANIM_SYS: &str = "anim_sys"; +const ANIMATION_SYS: &str = "animation_sys"; +const STATS_SYS: &str = "stats_sys"; pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); dispatch_builder.add(phys::Sys, PHYS_SYS, &[]); - dispatch_builder.add(control::Sys, CONTROL_SYS, &["phys_sys"]); - dispatch_builder.add(anim::Sys, ANIM_SYS, &[]); + dispatch_builder.add(actions::Sys, ACTIONS_SYS, &[]); + dispatch_builder.add(inputs::Sys, INPUTS_SYS, &[]); + dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[]); + dispatch_builder.add(stats::Sys, STATS_SYS, &[INPUTS_SYS]); } diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs new file mode 100644 index 0000000000..96eacddb7a --- /dev/null +++ b/common/src/sys/stats.rs @@ -0,0 +1,32 @@ +// Library +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use vek::*; + +// Crate +use crate::{ + comp::{Dying, Stats}, + state::DeltaTime, +}; + +// Basic ECS AI agent system +pub struct Sys; + +impl<'a> System<'a> for Sys { + type SystemData = ( + Entities<'a>, + Read<'a, DeltaTime>, + WriteStorage<'a, Stats>, + WriteStorage<'a, Dying>, + ); + + fn run(&mut self, (entities, dt, mut stats, mut dyings): Self::SystemData) { + for (entity, mut stat) in (&entities, &mut stats).join() { + if stat.hp.current == 0 { + dyings.insert(entity, Dying); + } + if let Some(change) = &mut stat.hp.last_change { + change.1 += dt.0 as f64; + } + } + } +} diff --git a/common/src/volumes/vol_map_2d.rs b/common/src/volumes/vol_map_2d.rs index 79db65cee4..b552778a04 100644 --- a/common/src/volumes/vol_map_2d.rs +++ b/common/src/volumes/vol_map_2d.rs @@ -6,7 +6,11 @@ use crate::{ dyna::{Dyna, DynaErr}, }, }; -use std::{collections::HashMap, marker::PhantomData, sync::Arc}; +use std::{ + collections::{hash_map, HashMap}, + marker::PhantomData, + sync::Arc, +}; use vek::*; #[derive(Debug)] @@ -145,6 +149,14 @@ impl VolMap2d { self.chunks.get(&key) } + pub fn clear(&mut self) { + self.chunks.clear(); + } + + pub fn drain(&mut self) -> hash_map::Drain, Arc> { + self.chunks.drain() + } + pub fn remove(&mut self, key: Vec2) -> Option> { self.chunks.remove(&key) } diff --git a/server/src/client.rs b/server/src/client.rs index d4532d2966..61cdf9815a 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -47,6 +47,14 @@ impl Clients { self.clients.insert(entity, client); } + pub fn get<'a>(&'a self, entity: &EcsEntity) -> Option<&'a Client> { + self.clients.get(entity) + } + + pub fn get_mut<'a>(&'a mut self, entity: &EcsEntity) -> Option<&'a mut Client> { + self.clients.get_mut(entity) + } + pub fn remove_if bool>(&mut self, mut f: F) { self.clients.retain(|entity, client| !f(*entity, client)); } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b31ed2ee28..1c420cc701 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -75,6 +75,12 @@ lazy_static! { "/tp : Teleport to another player", handle_tp ), + ChatCommand::new( + "kill", + "{}", + "/kill : Kill yourself", + handle_kill + ), ChatCommand::new( "pet", "{}", @@ -132,6 +138,15 @@ fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &Ch } } +fn handle_kill(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + server + .state + .ecs_mut() + .write_storage::() + .get_mut(entity) + .map(|s| s.hp.current = 0); +} + fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { let opt_alias = scan_fmt!(&args, action.arg_fmt, String); match opt_alias { @@ -196,18 +211,18 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha .state .read_component_cloned::(entity) { - Some(pos) => { + Some(mut pos) => { + pos.0.x += 1.0; // Temp fix TODO: Solve NaN issue with positions of pets server .create_npc( + pos, "Bungo".to_owned(), comp::Body::Quadruped(comp::QuadrupedBody::random()), ) - .with(comp::Control::default()) .with(comp::Agent::Pet { target: entity, offset: Vec2::zero(), }) - .with(pos) .build(); server .clients diff --git a/server/src/lib.rs b/server/src/lib.rs index 3b98f194ce..b9e3fb4ce9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -73,7 +73,6 @@ impl Server { let (chunk_tx, chunk_rx) = mpsc::channel(); let mut state = State::new(); - state.ecs_mut().register::(); state .ecs_mut() .add_resource(SpawnPoint(Vec3::new(16_384.0, 16_384.0, 280.0))); @@ -99,7 +98,7 @@ impl Server { "Tobermory".to_owned(), comp::Body::Humanoid(comp::HumanoidBody::random()), ) - .with(comp::Control::default()) + .with(comp::Actions::default()) .with(comp::Agent::Wanderer(Vec2::zero())) .build(); } @@ -127,14 +126,20 @@ impl Server { /// Build a non-player character. #[allow(dead_code)] - pub fn create_npc(&mut self, name: String, body: comp::Body) -> EcsEntityBuilder { + pub fn create_npc( + &mut self, + pos: comp::phys::Pos, + name: String, + body: comp::Body, + ) -> EcsEntityBuilder { self.state .ecs_mut() .create_entity_synced() - .with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))) + .with(pos) + .with(comp::Control::default()) .with(comp::phys::Vel(Vec3::zero())) .with(comp::phys::Dir(Vec3::unit_y())) - .with(comp::AnimationHistory::new(comp::Animation::Idle)) + .with(comp::AnimationInfo::default()) .with(comp::Actor::Character { name, body }) .with(comp::Stats::default()) } @@ -149,18 +154,17 @@ impl Server { let spawn_point = state.ecs().read_resource::().0; state.write_component(entity, comp::Actor::Character { name, body }); + state.write_component(entity, comp::Stats::default()); + state.write_component(entity, comp::Control::default()); + state.write_component(entity, comp::AnimationInfo::default()); state.write_component(entity, comp::phys::Pos(spawn_point)); state.write_component(entity, comp::phys::Vel(Vec3::zero())); state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); - // Make sure everything is accepted. + // Make sure physics are accepted. state.write_component(entity, comp::phys::ForceUpdate); - // Set initial animation. - state.write_component(entity, comp::AnimationHistory::new(comp::Animation::Idle)); - // Tell the client its request was successful. - client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character))); - client.client_state = ClientState::Character; + client.allow_state(ClientState::Character); } /// Execute a single server tick, handle input and update the game state by the given duration. @@ -180,7 +184,7 @@ impl Server { // 6) Send relevant state updates to all clients // 7) Finish the tick, passing control of the main thread back to the frontend - // Build up a list of events for this frame, to be passed to the frontend. + // 1) Build up a list of events for this frame, to be passed to the frontend. let mut frontend_events = Vec::new(); // If networking has problems, handle them. @@ -188,19 +192,60 @@ impl Server { return Err(err.into()); } - // Handle new client connections (step 2). - frontend_events.append(&mut self.handle_new_connections()?); + // 2) - // Handle new messages from clients + // 3) Handle inputs from clients + frontend_events.append(&mut self.handle_new_connections()?); frontend_events.append(&mut self.handle_new_messages()?); - // Tick the client's LocalState (step 3). + // 4) Tick the client's LocalState. self.state.tick(dt); // Tick the world self.world.tick(dt); - // Fetch any generated `TerrainChunk`s and insert them into the terrain. + // Sync deaths. + let todo_kill = ( + &self.state.ecs().entities(), + &self.state.ecs().read_storage::(), + ) + .join() + .map(|(entity, _)| entity) + .collect::>(); + + for entity in todo_kill { + if let Some(client) = self.clients.get_mut(&entity) { + client.force_state(ClientState::Dead); + } else { + self.state.ecs_mut().delete_entity_synced(entity); + } + } + + // Handle respawns + let todo_respawn = ( + &self.state.ecs().entities(), + &self.state.ecs().read_storage::(), + ) + .join() + .map(|(entity, _)| entity) + .collect::>(); + + for entity in todo_respawn { + if let Some(client) = self.clients.get_mut(&entity) { + client.allow_state(ClientState::Character); + self.state.write_component(entity, comp::Stats::default()); + self.state + .ecs_mut() + .write_storage::() + .get_mut(entity) + .map(|pos| pos.0.z += 100.0); + self.state + .write_component(entity, comp::phys::Vel(Vec3::zero())); + self.state.write_component(entity, comp::phys::ForceUpdate); + } + } + + // 5) Fetch any generated `TerrainChunk`s and insert them into the terrain. // Also, send the chunk data to anybody that is close by. if let Ok((key, chunk)) = self.chunk_rx.try_recv() { // Send the chunk to all nearby players. @@ -261,10 +306,20 @@ impl Server { self.state.remove_chunk(key); } - // Synchronise clients with the new state of the world. + // 6) Synchronise clients with the new state of the world. self.sync_clients(); - // Finish the tick, pass control back to the frontend (step 6). + // 7) Finish the tick, pass control back to the frontend. + + // Cleanup + let ecs = self.state.ecs_mut(); + for entity in ecs.entities().join() { + ecs.write_storage::().remove(entity); + ecs.write_storage::().remove(entity); + ecs.write_storage::().remove(entity); + ecs.write_storage::().remove(entity); + } + Ok(frontend_events) } @@ -331,9 +386,10 @@ impl Server { ClientState::Registered => { client.error_state(RequestStateError::Already) } - ClientState::Spectator | ClientState::Character => { - client.allow_state(ClientState::Registered) - } + ClientState::Spectator + | ClientState::Character + | ClientState::Dead => client.allow_state(ClientState::Registered), + ClientState::Pending => {} }, ClientState::Spectator => match requested_state { // Become Registered first. @@ -343,14 +399,17 @@ impl Server { ClientState::Spectator => { client.error_state(RequestStateError::Already) } - ClientState::Registered | ClientState::Character => { - client.allow_state(ClientState::Spectator) - } + ClientState::Registered + | ClientState::Character + | ClientState::Dead => client.allow_state(ClientState::Spectator), + ClientState::Pending => {} }, // Use ClientMsg::Character instead. ClientState::Character => { client.error_state(RequestStateError::WrongMessage) } + ClientState::Dead => client.error_state(RequestStateError::Impossible), + ClientState::Pending => {} }, ClientMsg::Register { player } => match client.client_state { ClientState::Connected => { @@ -374,12 +433,27 @@ impl Server { ClientState::Connected => { client.error_state(RequestStateError::Impossible) } - ClientState::Registered | ClientState::Spectator => { + ClientState::Registered + | ClientState::Spectator + | ClientState::Dead => { Self::create_player_character(state, entity, client, name, body) } ClientState::Character => { client.error_state(RequestStateError::Already) } + ClientState::Pending => {} + }, + ClientMsg::Attack => match client.client_state { + ClientState::Character => { + state.write_component(entity, comp::Attacking::start()); + } + _ => client.error_state(RequestStateError::Impossible), + }, + ClientMsg::Respawn => match client.client_state { + ClientState::Dead => { + state.write_component(entity, comp::Respawning); + } + _ => client.error_state(RequestStateError::Impossible), }, ClientMsg::Chat(msg) => match client.client_state { ClientState::Connected => { @@ -387,12 +461,14 @@ impl Server { } ClientState::Registered | ClientState::Spectator + | ClientState::Dead | ClientState::Character => new_chat_msgs.push((entity, msg)), + ClientState::Pending => {} }, - ClientMsg::PlayerAnimation(animation_history) => { + ClientMsg::PlayerAnimation(animation_info) => { match client.client_state { ClientState::Character => { - state.write_component(entity, animation_history) + state.write_component(entity, animation_info) } // Only characters can send animations. _ => client.error_state(RequestStateError::Impossible), @@ -408,7 +484,9 @@ impl Server { _ => client.error_state(RequestStateError::Impossible), }, ClientMsg::TerrainChunkRequest { key } => match client.client_state { - ClientState::Connected | ClientState::Registered => { + ClientState::Connected + | ClientState::Registered + | ClientState::Dead => { client.error_state(RequestStateError::Impossible); } ClientState::Spectator | ClientState::Character => { @@ -422,6 +500,7 @@ impl Server { None => requested_chunks.push(key), } } + ClientState::Pending => {} }, // Always possible. ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong), @@ -491,18 +570,35 @@ impl Server { // Save player metadata (for example the username). state.write_component(entity, player); - // Sync logical information other players have authority over, not the server. - for (other_entity, &uid, &animation_history) in ( + // Sync physics + for (entity, &uid, &pos, &vel, &dir) in ( &state.ecs().entities(), - &state.ecs().read_storage::(), - &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), ) .join() { - // AnimationHistory - client.postbox.send_message(ServerMsg::EntityAnimation { + client.notify(ServerMsg::EntityPhysics { entity: uid.into(), - animation_history: animation_history, + pos, + vel, + dir, + }); + } + + // Sync animations + for (entity, &uid, &animation_info) in ( + &state.ecs().entities(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + ) + .join() + { + client.notify(ServerMsg::EntityAnimation { + entity: uid.into(), + animation_info: animation_info.clone(), }); } @@ -516,7 +612,7 @@ impl Server { self.clients .notify_registered(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package())); - // Sync 'physical' state. + // Sync physics for (entity, &uid, &pos, &vel, &dir, force_update) in ( &self.state.ecs().entities(), &self.state.ecs().read_storage::(), @@ -538,41 +634,33 @@ impl Server { }; match force_update { - Some(_) => self.clients.notify_ingame(msg), - None => self.clients.notify_ingame_except(entity, msg), + Some(_) => self.clients.notify_registered(msg), + None => self.clients.notify_registered_except(entity, msg), } } - // Sync animation states. - for (entity, &uid, &animation_history) in ( + // Sync animations + for (entity, &uid, &animation_info, force_update) in ( &self.state.ecs().entities(), &self.state.ecs().read_storage::(), - &self.state.ecs().read_storage::(), + &self.state.ecs().read_storage::(), + self.state + .ecs() + .read_storage::() + .maybe(), ) .join() { - // Check if we need to sync. - if Some(animation_history.current) == animation_history.last { - continue; - } - - self.clients.notify_ingame_except( - entity, - ServerMsg::EntityAnimation { + if animation_info.changed || force_update.is_some() { + let msg = ServerMsg::EntityAnimation { entity: uid.into(), - animation_history, - }, - ); - } - - // Update animation last/current state. - for (entity, mut animation_history) in ( - &self.state.ecs().entities(), - &mut self.state.ecs().write_storage::(), - ) - .join() - { - animation_history.last = Some(animation_history.current); + animation_info: animation_info.clone(), + }; + match force_update { + Some(_) => self.clients.notify_registered(msg), + None => self.clients.notify_registered_except(entity, msg), + } + } } // Remove all force flags. diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index a9aeb3c753..75d7117537 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -40,10 +40,9 @@ lazy_static = "1.1" log = "0.4" dot_vox = "4.0" image = "0.21" -config = "0.9" serde = "1.0" serde_derive = "1.0" -toml = "0.4" +ron = "0.5.1" guillotiere = "0.4" fnv = "1.0" simplelog = "0.5" diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index eb4bf1a951..032e214275 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -27,7 +27,7 @@ use crate::{ scene::camera::Camera, settings::{ControlSettings, Settings}, ui::{Ingame, Ingameable, ScaleMode, Ui}, - window::{Event as WinEvent, Key, Window}, + window::{Event as WinEvent, GameInput, Window}, GlobalState, }; use client::Client; @@ -305,10 +305,10 @@ impl Hud { let mut name_id_walker = self.ids.name_tags.walk(); let mut health_id_walker = self.ids.health_bars.walk(); let mut health_back_id_walker = self.ids.health_bar_backs.walk(); - for (pos, name) in (&entities, &pos, &actor, player.maybe()) + for (pos, name) in (&entities, &pos, &actor, &stats, player.maybe()) .join() - .filter(|(entity, _, _, _)| *entity != me) - .map(|(entity, pos, actor, player)| match actor { + .filter(|(entity, _, _, stats, _)| *entity != me && !stats.is_dead()) + .map(|(entity, pos, actor, _, player)| match actor { comp::Actor::Character { name: char_name, .. } => { @@ -335,15 +335,10 @@ impl Hud { .resolution(100.0) .set(id, ui_widgets); } - for (pos, hp) in (&entities, &pos, &stats) + + for (entity, pos, stats) in (&entities, &pos, &stats) .join() - .filter_map(|(entity, pos, stats)| { - if entity != me { - Some((pos.0, stats.hp)) - } else { - None - } - }) + .filter(|(entity, _, stats)| *entity != me && !stats.is_dead()) { let back_id = health_back_id_walker.next( &mut self.ids.health_bar_backs, @@ -356,17 +351,20 @@ impl Hud { // Healh Bar Rectangle::fill_with([120.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5)) .x_y(0.0, -25.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 3.0)) + .position_ingame(pos.0 + Vec3::new(0.0, 0.0, 3.0)) .resolution(100.0) .set(back_id, ui_widgets); // Filling Rectangle::fill_with( - [120.0 * (hp.current as f64 / hp.maximum as f64), 8.0], + [ + 120.0 * (stats.hp.current as f64 / stats.hp.maximum as f64), + 8.0, + ], HP_COLOR, ) .x_y(0.0, -25.0) - .position_ingame(pos + Vec3::new(0.0, 0.0, 3.0)) + .position_ingame(pos.0 + Vec3::new(0.0, 0.0, 3.0)) .resolution(100.0) .set(bar_id, ui_widgets); } @@ -624,20 +622,10 @@ impl Hud { } true } - WinEvent::KeyDown(Key::ToggleInterface) => { - self.show.toggle_ui(); - true - } - WinEvent::KeyDown(Key::ToggleCursor) => { - self.force_ungrab = !self.force_ungrab; - if self.force_ungrab { - global_state.window.grab_cursor(false); - } - true - } _ if !self.show.ui => false, WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(), - WinEvent::KeyDown(Key::Enter) => { + + WinEvent::InputUpdate(GameInput::Enter, true) => { self.ui.focus_widget(if self.typing() { None } else { @@ -645,7 +633,7 @@ impl Hud { }); true } - WinEvent::KeyDown(Key::Escape) => { + WinEvent::InputUpdate(GameInput::Escape, true) => { if self.typing() { self.ui.focus_widget(None); } else { @@ -654,54 +642,66 @@ impl Hud { } true } - WinEvent::KeyDown(key) if !self.typing() => match key { - Key::Map => { + + // Press key while not typing + WinEvent::InputUpdate(key, true) if !self.typing() => match key { + GameInput::ToggleInterface => { + self.show.toggle_ui(); + true + } + GameInput::ToggleCursor => { + self.force_ungrab = !self.force_ungrab; + if self.force_ungrab { + global_state.window.grab_cursor(false); + } + true + } + GameInput::Map => { self.show.toggle_map(); true } - Key::Bag => { + GameInput::Bag => { self.show.toggle_bag(); true } - Key::QuestLog => { + GameInput::QuestLog => { self.show.toggle_small(SmallWindowType::QuestLog); true } - Key::CharacterWindow => { + GameInput::CharacterWindow => { self.show.toggle_char_window(); true } - Key::Social => { + GameInput::Social => { self.show.toggle_small(SmallWindowType::Social); true } - Key::Spellbook => { + GameInput::Spellbook => { self.show.toggle_small(SmallWindowType::Spellbook); true } - Key::Settings => { + GameInput::Settings => { self.show.toggle_settings(); true } - Key::Help => { + GameInput::Help => { self.show.toggle_help(); true } - Key::ToggleDebug => { + GameInput::ToggleDebug => { self.show.debug = !self.show.debug; true } - Key::ToggleIngameUi => { + GameInput::ToggleIngameUi => { self.show.ingame = !self.show.ingame; true } _ => false, }, - WinEvent::KeyDown(key) | WinEvent::KeyUp(key) => match key { - Key::ToggleCursor => false, - _ => self.typing(), - }, + // Else the player is typing in chat + WinEvent::InputUpdate(key, _) => self.typing(), WinEvent::Char(_) => self.typing(), + _ => false, }; // Handle cursor grab. diff --git a/voxygen/src/key_state.rs b/voxygen/src/key_state.rs index 909f1f3490..2921c4ce6c 100644 --- a/voxygen/src/key_state.rs +++ b/voxygen/src/key_state.rs @@ -5,8 +5,6 @@ pub struct KeyState { pub left: bool, pub up: bool, pub down: bool, - pub jump: bool, - pub glide: bool, } impl KeyState { @@ -16,8 +14,6 @@ impl KeyState { left: false, up: false, down: false, - jump: false, - glide: false, } } diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index f48ea71175..f81f17c803 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,4 +1,5 @@ #![feature(drain_filter)] +#![feature(type_alias_enum_variants)] #![recursion_limit = "2048"] #[macro_use] diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 4a75a4b3ba..d99e739831 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -8,7 +8,7 @@ use crate::{ Direction, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{clock::Clock, comp, msg::ClientMsg}; +use common::{clock::Clock, comp, msg::ClientMsg, msg::ClientState}; use scene::Scene; use std::{cell::RefCell, rc::Rc, time::Duration}; use ui::CharSelectionUi; @@ -46,7 +46,8 @@ impl PlayState for CharSelectionState { // Set up an fps clock. let mut clock = Clock::new(); - loop { + let mut current_client_state = self.client.borrow().get_client_state(); + while let ClientState::Pending | ClientState::Registered = current_client_state { // Handle window events. for event in global_state.window.fetch_events() { match event { @@ -74,14 +75,11 @@ impl PlayState for CharSelectionState { return PlayStateResult::Pop; } ui::Event::Play => { - self.client - .borrow_mut() - .postbox - .send_message(ClientMsg::Character { - name: self.char_selection_ui.character_name.clone(), - body: comp::Body::Humanoid(self.char_selection_ui.character_body), - }); - return PlayStateResult::Switch(Box::new(SessionState::new( + self.client.borrow_mut().request_character( + self.char_selection_ui.character_name.clone(), + comp::Body::Humanoid(self.char_selection_ui.character_body), + ); + return PlayStateResult::Push(Box::new(SessionState::new( &mut global_state.window, self.client.clone(), global_state.settings.clone(), @@ -109,10 +107,14 @@ impl PlayState for CharSelectionState { .render(global_state.window.renderer_mut(), self.scene.globals()); // Tick the client (currently only to keep the connection alive). - self.client + if let Err(err) = self + .client .borrow_mut() - .tick(client::Input::default(), clock.get_last_delta()) - .expect("Failed to tick the client"); + .tick(comp::Control::default(), clock.get_last_delta()) + { + log::error!("Failed to tick the scene: {:?}", err); + return PlayStateResult::Pop; + } self.client.borrow_mut().cleanup(); // Finish the frame. @@ -124,7 +126,11 @@ impl PlayState for CharSelectionState { // Wait for the next tick. clock.tick(Duration::from_millis(1000 / FPS)); + + current_client_state = self.client.borrow().get_client_state(); } + + PlayStateResult::Pop } fn name(&self) -> &'static str { diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 48fbe30c87..fb57e018dd 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -23,12 +23,15 @@ use common::{ }, figure::Segment, msg, + msg::ClientState, }; use dot_vox::DotVoxData; use specs::{Component, Entity as EcsEntity, Join, VecStorage}; use std::{collections::HashMap, f32}; use vek::*; +const DAMAGE_FADE_COEFFICIENT: f64 = 5.0; + pub struct FigureModelCache { models: HashMap, u64)>, } @@ -347,17 +350,27 @@ impl FigureMgr { pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { let time = client.state().get_time(); let ecs = client.state().ecs(); - for (entity, pos, vel, dir, actor, animation_history, stats) in ( + for (entity, pos, vel, dir, actor, animation_info, stats) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), - &ecs.read_storage::(), + &ecs.read_storage::(), ecs.read_storage::().maybe(), ) .join() { + // Change in health as color! + let col = stats + .and_then(|stats| stats.hp.last_change) + .map(|(change_by, time)| { + Rgba::broadcast(1.0) + + Rgba::new(0.0, -1.0, -1.0, 0.0) + .map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32) + }) + .unwrap_or(Rgba::broadcast(1.0)); + match actor { comp::Actor::Character { body, .. } => match body { Body::Humanoid(body) => { @@ -365,60 +378,59 @@ impl FigureMgr { FigureState::new(renderer, CharacterSkeleton::new()) }); - let target_skeleton = match animation_history.current { + let target_skeleton = match animation_info.animation { comp::Animation::Idle => character::IdleAnimation::update_skeleton( state.skeleton_mut(), time, - animation_history.time, + animation_info.time, ), comp::Animation::Run => character::RunAnimation::update_skeleton( state.skeleton_mut(), (vel.0.magnitude(), time), - animation_history.time, + animation_info.time, ), comp::Animation::Jump => character::JumpAnimation::update_skeleton( state.skeleton_mut(), time, - animation_history.time, + animation_info.time, ), comp::Animation::Attack => character::AttackAnimation::update_skeleton( state.skeleton_mut(), time, - animation_history.time, + animation_info.time, ), comp::Animation::Gliding => { character::GlidingAnimation::update_skeleton( state.skeleton_mut(), time, - animation_history.time, + animation_info.time, ) } }; state.skeleton.interpolate(&target_skeleton); - - state.update(renderer, pos.0, dir.0, Rgba::white()); + state.update(renderer, pos.0, dir.0, col); } Body::Quadruped(body) => { let state = self.quadruped_states.entry(entity).or_insert_with(|| { FigureState::new(renderer, QuadrupedSkeleton::new()) }); - let target_skeleton = match animation_history.current { + let target_skeleton = match animation_info.animation { comp::Animation::Run => quadruped::RunAnimation::update_skeleton( state.skeleton_mut(), (vel.0.magnitude(), time), - animation_history.time, + animation_info.time, ), comp::Animation::Idle => quadruped::IdleAnimation::update_skeleton( state.skeleton_mut(), time, - animation_history.time, + animation_info.time, ), comp::Animation::Jump => quadruped::JumpAnimation::update_skeleton( state.skeleton_mut(), (vel.0.magnitude(), time), - animation_history.time, + animation_info.time, ), // TODO! @@ -426,21 +438,6 @@ impl FigureMgr { }; state.skeleton.interpolate(&target_skeleton); - - // Change in health as color! - let col = stats - .and_then(|stats| stats.hp.last_change) - .map(|(change_by, change_time)| Rgba::new(1.0, 0.7, 0.7, 1.0)) - .unwrap_or(Rgba::broadcast(1.0)); - - // Change in health as color! - let col = stats - .and_then(|stats| stats.hp.last_change) - .map(|(change_by, change_time)| Rgba::new(1.0, 0.7, 0.7, 1.0)) - .unwrap_or(Rgba::broadcast(1.0)); - - state.update(renderer, pos.0, dir.0, col); - state.update(renderer, pos.0, dir.0, col); } }, @@ -464,7 +461,17 @@ impl FigureMgr { let tick = client.get_tick(); let ecs = client.state().ecs(); - for (entity, actor) in (&ecs.entities(), &ecs.read_storage::()).join() { + for (entity, actor, stat) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), // Just to make sure the entity is alive + ) + .join() + { + if stat.is_dead() { + return; + } + match actor { comp::Actor::Character { body, .. } => { if let Some((locals, bone_consts)) = match body { @@ -480,6 +487,8 @@ impl FigureMgr { let model = self.model_cache.get_or_create_model(renderer, *body, tick); renderer.render_figure(model, globals, locals, bone_consts); + } else { + log::error!("Body has no saved figure"); } } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index a74c01fd0f..1b51b9d4f0 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -4,11 +4,12 @@ use crate::{ render::Renderer, scene::Scene, settings::Settings, - window::{Event, Key, Window}, + window::{Event, GameInput, Window}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; -use client::{self, Client, Input, InputEvent}; -use common::clock::Clock; +use client::{self, Client}; +use common::{clock::Clock, comp, msg::ClientState}; +use glutin::MouseButton; use std::{cell::RefCell, mem, rc::Rc, time::Duration}; use vek::*; @@ -18,7 +19,6 @@ pub struct SessionState { scene: Scene, client: Rc>, key_state: KeyState, - input_events: Vec, hud: Hud, } @@ -33,7 +33,6 @@ impl SessionState { client, key_state: KeyState::new(), hud: Hud::new(window), - input_events: Vec::new(), } } } @@ -59,19 +58,11 @@ impl SessionState { let dir_vec = self.key_state.dir_vec(); let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1]; - // Take the input events. - let mut input_events = Vec::new(); - mem::swap(&mut self.input_events, &mut input_events); - - for event in self.client.borrow_mut().tick( - Input { - move_dir, - jumping: self.key_state.jump, - gliding: self.key_state.glide, - events: input_events, - }, - dt, - )? { + for event in self + .client + .borrow_mut() + .tick(comp::Control { move_dir }, dt)? + { match event { client::Event::Chat(msg) => { self.hud.new_message(msg); @@ -114,66 +105,57 @@ impl PlayState for SessionState { // Set up an fps clock. let mut clock = Clock::new(); - - // Load a few chunks. TODO: Remove this. - /* - for x in -6..7 { - for y in -6..7 { - for z in -1..2 { - self.client.borrow_mut().load_chunk(Vec3::new(x, y, z)); - } - } - } - */ + self.client.borrow_mut().clear_terrain(); // Game loop - loop { + let mut current_client_state = self.client.borrow().get_client_state(); + while let ClientState::Pending | ClientState::Character | ClientState::Dead = + current_client_state + { + let alive = self.client.borrow().get_client_state() == ClientState::Character; + // Handle window events. for event in global_state.window.fetch_events() { // Pass all events to the ui first. if self.hud.handle_event(event.clone(), global_state) { continue; } - let _handled = match event { + + match event { Event::Close => { return PlayStateResult::Shutdown; } - // Movement Key Pressed - Event::KeyDown(Key::MoveForward) => self.key_state.up = true, - Event::KeyDown(Key::MoveBack) => self.key_state.down = true, - Event::KeyDown(Key::MoveLeft) => self.key_state.left = true, - Event::KeyDown(Key::MoveRight) => self.key_state.right = true, - Event::KeyDown(Key::Jump) => { - self.input_events.push(InputEvent::Jump); - self.key_state.jump = true; + Event::InputUpdate(GameInput::Attack, true) => { + self.client.borrow_mut().attack(); + self.client.borrow_mut().respawn(); } - Event::KeyDown(Key::Glide) => self.key_state.glide = true, - // Movement Key Released - Event::KeyUp(Key::MoveForward) => self.key_state.up = false, - Event::KeyUp(Key::MoveBack) => self.key_state.down = false, - Event::KeyUp(Key::MoveLeft) => self.key_state.left = false, - Event::KeyUp(Key::MoveRight) => self.key_state.right = false, - Event::KeyUp(Key::Jump) => self.key_state.jump = false, - Event::KeyUp(Key::Glide) => self.key_state.glide = false, + Event::InputUpdate(GameInput::Jump, true) => { + self.client.borrow_mut().jump(); + } + Event::InputUpdate(GameInput::MoveForward, state) => self.key_state.up = state, + Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state, + Event::InputUpdate(GameInput::MoveLeft, state) => self.key_state.left = state, + Event::InputUpdate(GameInput::MoveRight, state) => self.key_state.right = state, + Event::InputUpdate(GameInput::Glide, state) => { + self.client.borrow_mut().glide(state) + } + // Pass all other events to the scene event => { self.scene.handle_input_event(event); - } - }; - // TODO: Do something if the event wasn't handled? + } // TODO: Do something if the event wasn't handled? + } } // Perform an in-game tick. - self.tick(clock.get_last_delta()) - .expect("Failed to tick the scene!"); + if let Err(err) = self.tick(clock.get_last_delta()) { + log::error!("Failed to tick the scene: {:?}", err); + return PlayStateResult::Pop; + } // Maintain global state global_state.maintain(); - // Maintain the scene. - self.scene - .maintain(global_state.window.renderer_mut(), &self.client.borrow()); - // extract HUD events ensuring the client borrow gets dropped let hud_events = self.hud.maintain( &self.client.borrow(), @@ -215,7 +197,10 @@ impl PlayState for SessionState { } } } - {} + + // Maintain the scene. + self.scene + .maintain(global_state.window.renderer_mut(), &self.client.borrow()); // Render the session. self.render(global_state.window.renderer_mut()); @@ -231,7 +216,11 @@ impl PlayState for SessionState { // Clean things up after the tick. self.cleanup(); + + current_client_state = self.client.borrow().get_client_state(); } + + PlayStateResult::Pop } fn name(&self) -> &'static str { diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 3ba043737f..d92d5bd626 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -1,9 +1,8 @@ -use config::{Config, ConfigError}; +use crate::window::KeyMouse; use directories::ProjectDirs; -use glutin::VirtualKeyCode; +use glutin::{MouseButton, VirtualKeyCode}; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; -use toml; /// `Settings` contains everything that can be configured in the Settings.toml file. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -19,30 +18,31 @@ pub struct Settings { /// `ControlSettings` contains keybindings. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ControlSettings { - pub toggle_cursor: VirtualKeyCode, - pub escape: VirtualKeyCode, - pub enter: VirtualKeyCode, - pub move_forward: VirtualKeyCode, - pub move_left: VirtualKeyCode, - pub move_back: VirtualKeyCode, - pub move_right: VirtualKeyCode, - pub jump: VirtualKeyCode, - pub glide: VirtualKeyCode, - pub map: VirtualKeyCode, - pub bag: VirtualKeyCode, - pub quest_log: VirtualKeyCode, - pub character_window: VirtualKeyCode, - pub social: VirtualKeyCode, - pub spellbook: VirtualKeyCode, - pub settings: VirtualKeyCode, - pub help: VirtualKeyCode, - pub toggle_interface: VirtualKeyCode, - pub toggle_debug: VirtualKeyCode, - pub fullscreen: VirtualKeyCode, - pub screenshot: VirtualKeyCode, - pub toggle_ingame_ui: VirtualKeyCode, + pub toggle_cursor: KeyMouse, + pub escape: KeyMouse, + pub enter: KeyMouse, + pub move_forward: KeyMouse, + pub move_left: KeyMouse, + pub move_back: KeyMouse, + pub move_right: KeyMouse, + pub jump: KeyMouse, + pub glide: KeyMouse, + pub map: KeyMouse, + pub bag: KeyMouse, + pub quest_log: KeyMouse, + pub character_window: KeyMouse, + pub social: KeyMouse, + pub spellbook: KeyMouse, + pub settings: KeyMouse, + pub help: KeyMouse, + pub toggle_interface: KeyMouse, + pub toggle_debug: KeyMouse, + pub fullscreen: KeyMouse, + pub screenshot: KeyMouse, + pub toggle_ingame_ui: KeyMouse, pub pan_sensitivity: f32, pub zoom_sensitivity: f32, + pub attack: KeyMouse, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -77,30 +77,31 @@ impl Default for Settings { fn default() -> Self { Settings { controls: ControlSettings { - toggle_cursor: VirtualKeyCode::Tab, - escape: VirtualKeyCode::Escape, - enter: VirtualKeyCode::Return, - move_forward: VirtualKeyCode::W, - move_left: VirtualKeyCode::A, - move_back: VirtualKeyCode::S, - move_right: VirtualKeyCode::D, - jump: VirtualKeyCode::Space, - glide: VirtualKeyCode::LShift, - map: VirtualKeyCode::M, - bag: VirtualKeyCode::B, - quest_log: VirtualKeyCode::L, - character_window: VirtualKeyCode::C, - social: VirtualKeyCode::O, - spellbook: VirtualKeyCode::P, - settings: VirtualKeyCode::N, - help: VirtualKeyCode::F1, - toggle_interface: VirtualKeyCode::F2, - toggle_debug: VirtualKeyCode::F3, - fullscreen: VirtualKeyCode::F11, - screenshot: VirtualKeyCode::F4, - toggle_ingame_ui: VirtualKeyCode::F6, + toggle_cursor: KeyMouse::Key(VirtualKeyCode::Tab), + escape: KeyMouse::Key(VirtualKeyCode::Escape), + enter: KeyMouse::Key(VirtualKeyCode::Return), + move_forward: KeyMouse::Key(VirtualKeyCode::W), + move_left: KeyMouse::Key(VirtualKeyCode::A), + move_back: KeyMouse::Key(VirtualKeyCode::S), + move_right: KeyMouse::Key(VirtualKeyCode::D), + jump: KeyMouse::Key(VirtualKeyCode::Space), + glide: KeyMouse::Key(VirtualKeyCode::LShift), + map: KeyMouse::Key(VirtualKeyCode::M), + bag: KeyMouse::Key(VirtualKeyCode::B), + quest_log: KeyMouse::Key(VirtualKeyCode::L), + character_window: KeyMouse::Key(VirtualKeyCode::C), + social: KeyMouse::Key(VirtualKeyCode::O), + spellbook: KeyMouse::Key(VirtualKeyCode::P), + settings: KeyMouse::Key(VirtualKeyCode::N), + help: KeyMouse::Key(VirtualKeyCode::F1), + toggle_interface: KeyMouse::Key(VirtualKeyCode::F2), + toggle_debug: KeyMouse::Key(VirtualKeyCode::F3), + fullscreen: KeyMouse::Key(VirtualKeyCode::F11), + screenshot: KeyMouse::Key(VirtualKeyCode::F4), + toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), pan_sensitivity: 1.0, zoom_sensitivity: 1.0, + attack: KeyMouse::Mouse(MouseButton::Left), }, networking: NetworkingSettings { username: "Username".to_string(), @@ -126,28 +127,11 @@ impl Settings { let path = Settings::get_settings_path(); - let mut config = Config::new(); - - config - .merge( - Config::try_from(&default_settings) - .expect("Default settings struct could not be converted to Config!"), - ) - .unwrap(); - - // TODO: Log errors here. - // If merge or try_into fail, use the default settings. - match config.merge::>(path.into()) { - Ok(_) => match config.try_into() { - Ok(settings) => settings, - Err(_) => default_settings, - }, - Err(_) => { - // Maybe the file didn't exist. - // TODO: Handle this result. - default_settings.save_to_file(); - default_settings - } + // If file doesn't exist, use the default settings. + if let Ok(file) = fs::File::open(path) { + ron::de::from_reader(file).expect("Error parsing settings") + } else { + Self::default() } } @@ -159,7 +143,7 @@ impl Settings { } let mut config_file = fs::File::create(path)?; - let s: &str = &toml::to_string_pretty(self).unwrap(); + let s: &str = &ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); config_file.write_all(s.as_bytes()).unwrap(); Ok(()) } @@ -169,7 +153,7 @@ impl Settings { ProjectDirs::from("net", "veloren", "voxygen").expect("No home directory defined!"); let path = proj_dirs.config_dir(); path.join("settings"); - let path = path.with_extension("toml"); + let path = path.with_extension("ron"); path } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index fc43802698..8930ae7a4f 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -3,9 +3,68 @@ use crate::{ settings::Settings, ui, Error, }; +use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use vek::*; +/// Represents a key that the game recognises after keyboard mapping. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum GameInput { + ToggleCursor, + MoveForward, + MoveBack, + MoveLeft, + MoveRight, + Jump, + Glide, + Enter, + Escape, + Map, + Bag, + QuestLog, + CharacterWindow, + Social, + Spellbook, + Settings, + ToggleInterface, + Help, + ToggleDebug, + Fullscreen, + Screenshot, + ToggleIngameUi, + Attack, + Respawn, +} + +/// Represents an incoming event from the window. +#[derive(Clone)] +pub enum Event { + /// The window has been requested to close. + Close, + /// The window has been resized. + Resize(Vec2), + /// A key has been typed that corresponds to a specific character. + Char(char), + /// The cursor has been panned across the screen while grabbed. + CursorPan(Vec2), + /// The camera has been requested to zoom. + Zoom(f32), + /// A key that the game recognises has been pressed or released. + InputUpdate(GameInput, bool), + /// Event that the ui uses. + Ui(ui::Event), + // The view distance has been changed + ViewDistanceChanged(u32), + /// Game settings have changed. + SettingsChanged, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub enum KeyMouse { + Key(glutin::VirtualKeyCode), + Mouse(glutin::MouseButton), +} + pub struct Window { events_loop: glutin::EventsLoop, renderer: Renderer, @@ -15,7 +74,7 @@ pub struct Window { pub zoom_sensitivity: f32, fullscreen: bool, needs_refresh_resize: bool, - key_map: HashMap, + key_map: HashMap, supplement_events: Vec, focused: bool, } @@ -42,28 +101,38 @@ impl Window { .map_err(|err| Error::BackendError(Box::new(err)))?; let mut key_map = HashMap::new(); - key_map.insert(settings.controls.toggle_cursor, Key::ToggleCursor); - key_map.insert(settings.controls.escape, Key::Escape); - key_map.insert(settings.controls.enter, Key::Enter); - key_map.insert(settings.controls.move_forward, Key::MoveForward); - key_map.insert(settings.controls.move_left, Key::MoveLeft); - key_map.insert(settings.controls.move_back, Key::MoveBack); - key_map.insert(settings.controls.move_right, Key::MoveRight); - key_map.insert(settings.controls.jump, Key::Jump); - key_map.insert(settings.controls.glide, Key::Glide); - key_map.insert(settings.controls.map, Key::Map); - key_map.insert(settings.controls.bag, Key::Bag); - key_map.insert(settings.controls.quest_log, Key::QuestLog); - key_map.insert(settings.controls.character_window, Key::CharacterWindow); - key_map.insert(settings.controls.social, Key::Social); - key_map.insert(settings.controls.spellbook, Key::Spellbook); - key_map.insert(settings.controls.settings, Key::Settings); - key_map.insert(settings.controls.help, Key::Help); - key_map.insert(settings.controls.toggle_interface, Key::ToggleInterface); - key_map.insert(settings.controls.toggle_debug, Key::ToggleDebug); - key_map.insert(settings.controls.fullscreen, Key::Fullscreen); - key_map.insert(settings.controls.screenshot, Key::Screenshot); - key_map.insert(settings.controls.toggle_ingame_ui, Key::ToggleIngameUi); + key_map.insert(settings.controls.toggle_cursor, GameInput::ToggleCursor); + key_map.insert(settings.controls.escape, GameInput::Escape); + key_map.insert(settings.controls.enter, GameInput::Enter); + key_map.insert(settings.controls.move_forward, GameInput::MoveForward); + key_map.insert(settings.controls.move_left, GameInput::MoveLeft); + key_map.insert(settings.controls.move_back, GameInput::MoveBack); + key_map.insert(settings.controls.move_right, GameInput::MoveRight); + key_map.insert(settings.controls.jump, GameInput::Jump); + key_map.insert(settings.controls.glide, GameInput::Glide); + key_map.insert(settings.controls.map, GameInput::Map); + key_map.insert(settings.controls.bag, GameInput::Bag); + key_map.insert(settings.controls.quest_log, GameInput::QuestLog); + key_map.insert( + settings.controls.character_window, + GameInput::CharacterWindow, + ); + key_map.insert(settings.controls.social, GameInput::Social); + key_map.insert(settings.controls.spellbook, GameInput::Spellbook); + key_map.insert(settings.controls.settings, GameInput::Settings); + key_map.insert(settings.controls.help, GameInput::Help); + key_map.insert( + settings.controls.toggle_interface, + GameInput::ToggleInterface, + ); + key_map.insert(settings.controls.toggle_debug, GameInput::ToggleDebug); + key_map.insert(settings.controls.fullscreen, GameInput::Fullscreen); + key_map.insert(settings.controls.screenshot, GameInput::Screenshot); + key_map.insert( + settings.controls.toggle_ingame_ui, + GameInput::ToggleIngameUi, + ); + key_map.insert(settings.controls.attack, GameInput::Attack); Ok(Self { events_loop, @@ -124,24 +193,31 @@ impl Window { events.push(Event::Resize(Vec2::new(width as u32, height as u32))); } glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), - + glutin::WindowEvent::MouseInput { button, state, .. } if cursor_grabbed => { + if let Some(&game_input) = key_map.get(&KeyMouse::Mouse(button)) { + events.push(Event::InputUpdate( + game_input, + state == glutin::ElementState::Pressed, + )) + } + } glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode { - Some(keycode) => match key_map.get(&keycode) { - Some(Key::Fullscreen) => match input.state { + Some(key) => match key_map.get(&KeyMouse::Key(key)) { + Some(GameInput::Fullscreen) => match input.state { glutin::ElementState::Pressed => { toggle_fullscreen = !toggle_fullscreen } _ => (), }, - Some(Key::Screenshot) => match input.state { + Some(GameInput::Screenshot) => match input.state { glutin::ElementState::Pressed => take_screenshot = true, _ => {} }, - Some(&key) => events.push(match input.state { - glutin::ElementState::Pressed => Event::KeyDown(key), - glutin::ElementState::Released => Event::KeyUp(key), - }), + Some(&game_input) => events.push(Event::InputUpdate( + game_input, + input.state == glutin::ElementState::Pressed, + )), _ => {} }, _ => {} @@ -254,55 +330,3 @@ impl Window { } } } - -/// Represents a key that the game recognises after keyboard mapping. -#[derive(Clone, Copy)] -pub enum Key { - ToggleCursor, - MoveForward, - MoveBack, - MoveLeft, - MoveRight, - Jump, - Glide, - Enter, - Escape, - Map, - Bag, - QuestLog, - CharacterWindow, - Social, - Spellbook, - Settings, - ToggleInterface, - Help, - ToggleDebug, - Fullscreen, - Screenshot, - ToggleIngameUi, -} - -/// Represents an incoming event from the window. -#[derive(Clone)] -pub enum Event { - /// The window has been requested to close. - Close, - /// The window has been resized. - Resize(Vec2), - /// A key has been typed that corresponds to a specific character. - Char(char), - /// The cursor has been panned across the screen while grabbed. - CursorPan(Vec2), - /// The camera has been requested to zoom. - Zoom(f32), - /// A key that the game recognises has been pressed down. - KeyDown(Key), - /// A key that the game recognises has been released down. - KeyUp(Key), - /// Event that the ui uses. - Ui(ui::Event), - // The view distance has been changed - ViewDistanceChanged(u32), - /// Game settings have changed. - SettingsChanged, -}