Merge branch 'attack' into 'master'

Combat

Closes #59

See merge request veloren/veloren!153

Former-commit-id: c0c4583d162470b93beec31baf18b2f5999503ee
This commit is contained in:
Timo Koesters 2019-05-26 14:31:05 +00:00
commit dc7f1fe7aa
39 changed files with 1141 additions and 811 deletions

5
.gitignore vendored
View File

@ -15,10 +15,7 @@
*.code-workspace
# Veloren
voxygen/keybinds.toml
settings.toml
voxygen/settings/
*.rar
*.log
run.sh
screenshots
screenshots

118
Cargo.lock generated
View File

@ -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)" = "<none>"
"checksum conrod_derive 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "<none>"
"checksum conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "<none>"
@ -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"

View File

@ -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);

View File

@ -1,23 +0,0 @@
use vek::*;
pub enum InputEvent {
Jump,
}
pub struct Input {
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
pub events: Vec<InputEvent>,
}
impl Default for Input {
fn default() -> Self {
Input {
move_dir: Vec2::zero(),
jumping: false,
gliding: false,
events: Vec::new(),
}
}
}

View File

@ -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<ClientState>,
client_state: ClientState,
thread_pool: ThreadPool,
last_ping: f64,
pub postbox: PostBox<ClientMsg, ServerMsg>,
postbox: PostBox<ClientMsg, ServerMsg>,
last_server_ping: Instant,
last_ping_delta: f64,
@ -55,11 +51,11 @@ impl Client {
/// Create a new `Client`.
#[allow(dead_code)]
pub fn new<A: Into<SocketAddr>>(addr: A, view_distance: Option<u32>) -> Result<Self, Error> {
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::<comp::Gliding>()
.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<Vec<Event>, Error> {
pub fn tick(&mut self, control: comp::Control, dt: Duration) -> Result<Vec<Event>, 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::<comp::AnimationHistory>()
.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::<comp::phys::Pos>()
@ -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::<comp::AnimationInfo>()
.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::<comp::Jumping>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Dying>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Respawning>()
.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 {

View File

@ -229,33 +229,3 @@ pub enum Actor {
impl Component for Actor {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct AnimationHistory {
pub last: Option<Animation>,
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<Self, VecStorage<Self>>;
}

View File

@ -13,24 +13,3 @@ pub enum Agent {
impl Component for Agent {
type Storage = VecStorage<Self>;
}
#[derive(Copy, Clone, Debug)]
pub struct Control {
pub move_dir: Vec2<f32>,
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<Self>;
}

View File

@ -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<Self, VecStorage<Self>>;
}

49
common/src/comp/inputs.rs Normal file
View File

@ -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<f32>,
}
#[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<Self>;
}
impl Component for Respawning {
type Storage = NullStorage<Self>;
}
impl Attacking {
pub fn start() -> Self {
Self {
time: 0.0,
applied: false,
}
}
}
impl Component for Attacking {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
impl Component for Jumping {
type Storage = NullStorage<Self>;
}
impl Component for Gliding {
type Storage = NullStorage<Self>;
}

View File

@ -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;

View File

@ -28,7 +28,7 @@ impl Component for Dir {
type Storage = VecStorage<Self>;
}
// Update
// ForceUpdate
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ForceUpdate;

View File

@ -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<Self, VecStorage<Self>>;
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Respawn;
impl Component for Respawn {
type Storage = NullStorage<Self>;
}

View File

@ -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<Self, VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Dying;
impl Component for Dying {
type Storage = NullStorage<Self>;
}

View File

@ -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,

View File

@ -23,6 +23,7 @@ sphynx::sum_type! {
Actor(comp::Actor),
Player(comp::Player),
Stats(comp::Stats),
Attacking(comp::Attacking),
}
}
// Automatically derive From<T> for EcsCompPhantom
@ -36,6 +37,7 @@ sphynx::sum_type! {
Actor(PhantomData<comp::Actor>),
Player(PhantomData<comp::Player>),
Stats(PhantomData<comp::Stats>),
Attacking(PhantomData<comp::Attacking>),
}
}
impl sphynx::CompPacket for EcsCompPacket {

View File

@ -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,
}

View File

@ -31,7 +31,7 @@ pub enum ServerMsg {
},
EntityAnimation {
entity: u64,
animation_history: comp::AnimationHistory,
animation_info: comp::AnimationInfo,
},
TerrainChunkUpdate {
key: Vec2<i32>,

View File

@ -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::<comp::Actor>();
ecs.register_synced::<comp::Player>();
ecs.register_synced::<comp::Stats>();
ecs.register_synced::<comp::Attacking>();
ecs.register::<comp::phys::ForceUpdate>();
// Register unsynced (or synced by other means) components.
ecs.register::<comp::phys::Pos>();
ecs.register::<comp::phys::Vel>();
ecs.register::<comp::phys::Dir>();
ecs.register::<comp::AnimationHistory>();
ecs.register::<comp::Agent>();
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Attacking>();
ecs.register::<comp::Control>();
ecs.register::<comp::Jumping>();
ecs.register::<comp::Respawning>();
ecs.register::<comp::Gliding>();
ecs.register::<comp::Dying>();
ecs.register::<comp::Agent>();
ecs.register::<inventory::Inventory>();
// Register synced resources used by the ECS.
@ -181,6 +188,24 @@ impl State {
self.ecs.read_resource::<TerrainMap>()
}
/// Get a writable reference to this state's terrain.
pub fn terrain_mut(&self) -> FetchMut<TerrainMap> {
self.ecs.write_resource::<TerrainMap>()
}
/// Removes every chunk of the terrain.
pub fn clear_terrain(&mut self) {
let keys = self
.terrain_mut()
.drain()
.map(|(key, _)| key)
.collect::<Vec<_>>();
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<i32>, chunk: TerrainChunk) {
if self

43
common/src/sys/actions.rs Normal file
View File

@ -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::<Vec<_>>();
for entity in finished_attack {
attackings.remove(entity);
}
}
}

View File

@ -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::<f32>() - 0.5, rand::random::<f32>() - 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);

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

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

167
common/src/sys/inputs.rs Normal file
View File

@ -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::<f32>::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;
}
}
}
}

View File

@ -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]);
}

32
common/src/sys/stats.rs Normal file
View File

@ -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;
}
}
}
}

View File

@ -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<V: BaseVol, S: VolSize> VolMap2d<V, S> {
self.chunks.get(&key)
}
pub fn clear(&mut self) {
self.chunks.clear();
}
pub fn drain(&mut self) -> hash_map::Drain<Vec2<i32>, Arc<V>> {
self.chunks.drain()
}
pub fn remove(&mut self, key: Vec2<i32>) -> Option<Arc<V>> {
self.chunks.remove(&key)
}

View File

@ -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<F: FnMut(EcsEntity, &mut Client) -> bool>(&mut self, mut f: F) {
self.clients.retain(|entity, client| !f(*entity, client));
}

View File

@ -75,6 +75,12 @@ lazy_static! {
"/tp <alias> : 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::<comp::Stats>()
.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::<comp::phys::Pos>(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

View File

@ -73,7 +73,6 @@ impl Server {
let (chunk_tx, chunk_rx) = mpsc::channel();
let mut state = State::new();
state.ecs_mut().register::<comp::phys::ForceUpdate>();
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::<SpawnPoint>().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::<comp::Dying>(),
)
.join()
.map(|(entity, _)| entity)
.collect::<Vec<_>>();
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::<comp::Respawning>(),
)
.join()
.map(|(entity, _)| entity)
.collect::<Vec<EcsEntity>>();
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::<comp::phys::Pos>()
.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::<comp::Jumping>().remove(entity);
ecs.write_storage::<comp::Gliding>().remove(entity);
ecs.write_storage::<comp::Dying>().remove(entity);
ecs.write_storage::<comp::Respawning>().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::<common::state::Uid>(),
&state.ecs().read_storage::<comp::AnimationHistory>(),
&state.ecs().read_storage::<Uid>(),
&state.ecs().read_storage::<comp::phys::Pos>(),
&state.ecs().read_storage::<comp::phys::Vel>(),
&state.ecs().read_storage::<comp::phys::Dir>(),
)
.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::<Uid>(),
&state.ecs().read_storage::<comp::AnimationInfo>(),
)
.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::<Uid>(),
@ -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::<Uid>(),
&self.state.ecs().read_storage::<comp::AnimationHistory>(),
&self.state.ecs().read_storage::<comp::AnimationInfo>(),
self.state
.ecs()
.read_storage::<comp::phys::ForceUpdate>()
.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::<comp::AnimationHistory>(),
)
.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.

View File

@ -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"

View File

@ -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.

View File

@ -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,
}
}

View File

@ -1,4 +1,5 @@
#![feature(drain_filter)]
#![feature(type_alias_enum_variants)]
#![recursion_limit = "2048"]
#[macro_use]

View File

@ -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 {

View File

@ -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<Body, (Model<FigurePipeline>, 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::<comp::phys::Pos>(),
&ecs.read_storage::<comp::phys::Vel>(),
&ecs.read_storage::<comp::phys::Dir>(),
&ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::AnimationHistory>(),
&ecs.read_storage::<comp::AnimationInfo>(),
ecs.read_storage::<comp::Stats>().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::<comp::Actor>()).join() {
for (entity, actor, stat) in (
&ecs.entities(),
&ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::Stats>(), // 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");
}
}
}

View File

@ -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<RefCell<Client>>,
key_state: KeyState,
input_events: Vec<InputEvent>,
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 {

View File

@ -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::<config::File<_>>(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
}
}

View File

@ -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<u32>),
/// 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<f32>),
/// 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<glutin::VirtualKeyCode, Key>,
key_map: HashMap<KeyMouse, GameInput>,
supplement_events: Vec<Event>,
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<u32>),
/// 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<f32>),
/// 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,
}