mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'attack' into 'master'
Combat Closes #59 See merge request veloren/veloren!153 Former-commit-id: c0c4583d162470b93beec31baf18b2f5999503ee
This commit is contained in:
commit
dc7f1fe7aa
5
.gitignore
vendored
5
.gitignore
vendored
@ -15,10 +15,7 @@
|
||||
*.code-workspace
|
||||
|
||||
# Veloren
|
||||
voxygen/keybinds.toml
|
||||
settings.toml
|
||||
voxygen/settings/
|
||||
*.rar
|
||||
*.log
|
||||
run.sh
|
||||
screenshots
|
||||
screenshots
|
||||
|
118
Cargo.lock
generated
118
Cargo.lock
generated
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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>>;
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
32
common/src/comp/animation.rs
Normal file
32
common/src/comp/animation.rs
Normal 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
49
common/src/comp/inputs.rs
Normal 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>;
|
||||
}
|
@ -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;
|
||||
|
@ -28,7 +28,7 @@ impl Component for Dir {
|
||||
type Storage = VecStorage<Self>;
|
||||
}
|
||||
|
||||
// Update
|
||||
// ForceUpdate
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ForceUpdate;
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ pub enum ServerMsg {
|
||||
},
|
||||
EntityAnimation {
|
||||
entity: u64,
|
||||
animation_history: comp::AnimationHistory,
|
||||
animation_info: comp::AnimationInfo,
|
||||
},
|
||||
TerrainChunkUpdate {
|
||||
key: Vec2<i32>,
|
||||
|
@ -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
43
common/src/sys/actions.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
22
common/src/sys/animation.rs
Normal file
22
common/src/sys/animation.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
167
common/src/sys/inputs.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
32
common/src/sys/stats.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![feature(drain_filter)]
|
||||
#![feature(type_alias_enum_variants)]
|
||||
#![recursion_limit = "2048"]
|
||||
|
||||
#[macro_use]
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user