From fecbf3bd971b4adf93c1e1d105b12b2ddb83a3bd Mon Sep 17 00:00:00 2001 From: Paul Bellchambers Date: Sat, 27 Feb 2021 18:48:39 +0000 Subject: [PATCH] feat: initial implementation of monsters and stats (#14) *feat: initial implementation of monsters *feat: ability to load monsters and spawns from .json assets *feat: player and monster stats *feat: stat window and map location display *feat: implemented look command *fix: fixed bug with logged out player being collidable *chore: updated dependencies *chore: updated logo --- Cargo.lock | 246 +++++++++++------- README.md | 4 +- assets/logo/rustyhack-logo.png | Bin 11575 -> 16383 bytes assets/maps/Cave.map | 6 + assets/maps/Home.map | 30 +++ assets/maps/default.txt | 31 --- assets/monsters/Rat.json | 38 +++ assets/monsters/Snake.json | 38 +++ assets/monsters/Zombie.json | 38 +++ assets/spawns/Cave.spawn.json | 13 + assets/spawns/Home.spawn.json | 30 +++ rustyhack_client/Cargo.toml | 7 +- rustyhack_client/src/consts.rs | 6 +- rustyhack_client/src/game.rs | 55 ++-- rustyhack_client/src/game/commands.rs | 1 + .../src/game/commands/look_command.rs | 81 ++++++ rustyhack_client/src/game/input_handler.rs | 22 ++ rustyhack_client/src/main.rs | 1 + rustyhack_client/src/screens.rs | 43 +++ .../src/screens/bottom_text_window.rs | 19 ++ .../src/screens/side_status_bar.rs | 33 +++ .../src/screens/top_status_bar.rs | 17 ++ .../src/{game => screens}/viewport.rs | 70 ++--- rustyhack_client/src/setup.rs | 8 +- rustyhack_lib/Cargo.toml | 11 +- .../src/background_map/character_map.rs | 2 + rustyhack_lib/src/background_map/tiles.rs | 8 +- rustyhack_lib/src/consts.rs | 11 +- rustyhack_lib/src/ecs.rs | 1 + rustyhack_lib/src/ecs/components.rs | 33 +++ rustyhack_lib/src/ecs/monster.rs | 58 +++++ rustyhack_lib/src/ecs/player.rs | 14 +- rustyhack_lib/src/file_utils.rs | 21 ++ rustyhack_lib/src/lib.rs | 5 + rustyhack_server/Cargo.toml | 11 +- rustyhack_server/src/consts.rs | 6 + rustyhack_server/src/game.rs | 53 +++- rustyhack_server/src/game/background_map.rs | 28 +- rustyhack_server/src/game/map_state.rs | 56 ++++ rustyhack_server/src/game/monsters.rs | 217 +++++++++++++++ rustyhack_server/src/game/player_updates.rs | 39 ++- rustyhack_server/src/game/players.rs | 15 ++ rustyhack_server/src/game/spawns.rs | 75 ++++++ rustyhack_server/src/game/systems.rs | 119 ++++++--- rustyhack_server/src/setup.rs | 8 +- 45 files changed, 1350 insertions(+), 278 deletions(-) create mode 100644 assets/maps/Cave.map create mode 100644 assets/maps/Home.map delete mode 100644 assets/maps/default.txt create mode 100644 assets/monsters/Rat.json create mode 100644 assets/monsters/Snake.json create mode 100644 assets/monsters/Zombie.json create mode 100644 assets/spawns/Cave.spawn.json create mode 100644 assets/spawns/Home.spawn.json create mode 100644 rustyhack_client/src/game/commands.rs create mode 100644 rustyhack_client/src/game/commands/look_command.rs create mode 100644 rustyhack_client/src/game/input_handler.rs create mode 100644 rustyhack_client/src/screens.rs create mode 100644 rustyhack_client/src/screens/bottom_text_window.rs create mode 100644 rustyhack_client/src/screens/side_status_bar.rs create mode 100644 rustyhack_client/src/screens/top_status_bar.rs rename rustyhack_client/src/{game => screens}/viewport.rs (73%) create mode 100644 rustyhack_lib/src/ecs/monster.rs create mode 100644 rustyhack_lib/src/file_utils.rs create mode 100644 rustyhack_server/src/game/map_state.rs create mode 100644 rustyhack_server/src/game/monsters.rs create mode 100644 rustyhack_server/src/game/players.rs create mode 100644 rustyhack_server/src/game/spawns.rs diff --git a/Cargo.lock b/Cargo.lock index e9d16a8..d8bd3f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "atomic_refcell" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bc31dce067eab974c815a9deb95f6217806de7b53685d7fc31f8ccf3fb2539f" + [[package]] name = "autocfg" version = "1.0.1" @@ -17,9 +23,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bincode" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" dependencies = [ "byteorder", "serde", @@ -54,15 +60,9 @@ checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" - -[[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cfg-if" @@ -124,7 +124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", ] [[package]] @@ -135,19 +135,18 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", ] [[package]] name = "crossbeam-epoch" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60ab4a8dba064f2fbb5aa270c28da5cf4bbd0e72dae1140a6b0353a779dbe00" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "lazy_static", - "loom", "memoffset", "scopeguard", ] @@ -165,14 +164,13 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg", "cfg-if 1.0.0", "lazy_static", - "loom", ] [[package]] @@ -201,17 +199,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "downcast-rs" version = "1.2.0" @@ -233,19 +220,6 @@ dependencies = [ "serde", ] -[[package]] -name = "generator" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "winapi", -] - [[package]] name = "getrandom" version = "0.1.16" @@ -265,7 +239,16 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", ] [[package]] @@ -288,13 +271,19 @@ dependencies = [ [[package]] name = "itertools" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "laminar" version = "0.4.0" @@ -306,7 +295,7 @@ dependencies = [ "crossbeam-channel 0.4.4", "lazy_static", "log", - "rand", + "rand 0.7.3", "rand_pcg", ] @@ -318,13 +307,13 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "legion" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa40f1a5f64dfdc1830657e0e7dd2c28087c4e32a2a85fcf63a286c429edefc" +checksum = "6bfd53bb4690a5ab2bd6d1c683461ee52763afe0d000929743708a256d9d9b1f" dependencies = [ + "atomic_refcell", "bit-set", - "crossbeam-channel 0.4.4", - "derivative", + "crossbeam-channel 0.5.0", "downcast-rs", "erased-serde", "itertools", @@ -332,6 +321,7 @@ dependencies = [ "parking_lot", "paste", "rayon", + "scoped-tls-hkt", "serde", "smallvec", "thiserror", @@ -340,9 +330,9 @@ dependencies = [ [[package]] name = "legion_codegen" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24e58060e656eae6b87f83f14f41080656a930fba7ef299122e40eb8ccd307f" +checksum = "8ad5ad7a361d7b2522010335d95fa73135cb3c6816bef22cc7a5d5861587ae1b" dependencies = [ "proc-macro2", "quote", @@ -374,17 +364,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "loom" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4" -dependencies = [ - "cfg-if 1.0.0", - "generator", - "scoped-tls", -] - [[package]] name = "maybe-uninit" version = "2.0.0" @@ -408,9 +387,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95" +checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a" dependencies = [ "libc", "log", @@ -469,9 +448,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.5.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a" [[package]] name = "parking_lot" @@ -536,9 +515,21 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", ] [[package]] @@ -548,7 +539,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", ] [[package]] @@ -560,13 +561,31 @@ dependencies = [ "getrandom 0.1.16", ] +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.2", +] + [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", ] [[package]] @@ -575,7 +594,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -598,7 +617,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel 0.5.0", "crossbeam-deque", - "crossbeam-utils 0.8.2", + "crossbeam-utils 0.8.3", "lazy_static", "num_cpus", ] @@ -630,17 +649,12 @@ version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" -[[package]] -name = "rustversion" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" - [[package]] name = "rustyhack_client" -version = "0.1.1" +version = "0.2.0" dependencies = [ "bincode", + "chrono", "console_engine", "crossbeam-channel 0.4.4", "crossterm", @@ -654,15 +668,20 @@ dependencies = [ [[package]] name = "rustyhack_lib" -version = "0.1.1" +version = "0.2.0" dependencies = [ "console_engine", + "log", "serde", + "simplelog", + "strum", + "strum_macros", + "uuid", ] [[package]] name = "rustyhack_server" -version = "0.1.1" +version = "0.2.0" dependencies = [ "bincode", "console_engine", @@ -671,16 +690,25 @@ dependencies = [ "laminar", "legion", "log", + "rand 0.8.3", "rustyhack_lib", "serde", + "serde_json", "simplelog", + "uuid", ] [[package]] -name = "scoped-tls" -version = "1.0.0" +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" [[package]] name = "scopeguard" @@ -708,6 +736,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43535db9747a4ba938c0ce0a98cc631a46ebf943c9e1d604e091df6007620bf6" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook" version = "0.1.17" @@ -756,6 +795,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "strum" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" + +[[package]] +name = "strum_macros" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.60" @@ -807,14 +864,21 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + [[package]] name = "unicode-xid" version = "0.2.1" @@ -839,9 +903,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "winapi" diff --git a/README.md b/README.md index 66a3168..317f44b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Rustyhack Logo](https://github.com/pbellchambers/rustyhack-mmo/raw/main/assets/logo/rustyhack-logo.png "Rustyhack Logo") # Rustyhack MMO -Partly a sandbox for me learning rust, partly an ASCII "roguelike" MMORPG. Lacking a lot of basic features. Currently produces a client & server console program that allows a player to be created and move around a map with arrow keys. +Partly a sandbox for me learning rust, partly an ASCII "roguelike" MMORPG. Lacking a lot of basic features. Currently produces a client & server console program that allows a player to be created, and move around a map with arrow keys. [![Build status](https://img.shields.io/github/workflow/status/pbellchambers/rustybox/CI/main)](https://github.com/pbellchambers/rustybox/actions) [![Downloads](https://img.shields.io/github/downloads/pbellchambers/rustybox/total)](https://github.com/pbellchambers/rustybox/releases) @@ -15,7 +15,7 @@ Partly a sandbox for me learning rust, partly an ASCII "roguelike" MMORPG. Lacki 4. Run `rustyhack_client` from the command line 5. Connect client to server -Use arrow keys to move around, ctrl-q to quit. +Use arrow keys to move around, spacebar to look, ctrl-q to quit. ## Components - **rustyhack_client** - contains all the client code diff --git a/assets/logo/rustyhack-logo.png b/assets/logo/rustyhack-logo.png index 0095eb70d027483047d9c27001335880b3a24a49..0cfa13266a6882a88d6ee4d62c0f851e22201544 100644 GIT binary patch delta 10106 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 delta 5258 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 kbus_n`+xjTwlZ{u@2sr!%n"] edition = "2018" repository = "https://github.com/pbellchambers/rustyhack-mmo" @@ -16,6 +16,7 @@ console_engine = "1.5.0" crossterm = { version = "0.19.0", features = ["serde"] } laminar = "0.4.0" crossbeam-channel = "0.4.4" -serde = "1.0.123" +serde = { version = "1.0.123", features = ["derive"] } bincode = "1.3.1" -regex = "1.4.3" \ No newline at end of file +regex = "1.4.3" +chrono = "0.4.19" \ No newline at end of file diff --git a/rustyhack_client/src/consts.rs b/rustyhack_client/src/consts.rs index 3100032..38296bf 100644 --- a/rustyhack_client/src/consts.rs +++ b/rustyhack_client/src/consts.rs @@ -1,5 +1,7 @@ -pub(crate) const VIEWPORT_WIDTH: u32 = 41; -pub(crate) const VIEWPORT_HEIGHT: u32 = 15; +pub(crate) const CONSOLE_WIDTH: u32 = 71; +pub(crate) const CONSOLE_HEIGHT: u32 = 31; +pub(crate) const VIEWPORT_WIDTH: u32 = 51; +pub(crate) const VIEWPORT_HEIGHT: u32 = 19; pub(crate) const TARGET_FPS: u32 = 10; pub(crate) const LOG_NAME: &str = "rustyhack_client.log"; pub(crate) const GAME_TITLE: &str = "Rustyhack MMO"; diff --git a/rustyhack_client/src/game.rs b/rustyhack_client/src/game.rs index 2f60e41..44caf46 100644 --- a/rustyhack_client/src/game.rs +++ b/rustyhack_client/src/game.rs @@ -1,17 +1,19 @@ -mod map_handler; -mod new_player; -mod updates_handler; -pub(crate) mod viewport; - -use crate::consts::{GAME_TITLE, TARGET_FPS, VIEWPORT_HEIGHT, VIEWPORT_WIDTH}; -use crate::game::viewport::Viewport; -use crate::networking::message_handler; use console_engine::{ConsoleEngine, KeyCode, KeyModifiers}; use crossbeam_channel::{Receiver, Sender}; use laminar::{Packet, SocketEvent}; -use rustyhack_lib::message_handler::player_message::EntityUpdates; use std::collections::HashMap; -use std::process; + +use rustyhack_lib::message_handler::player_message::EntityUpdates; + +use crate::consts::{CONSOLE_HEIGHT, CONSOLE_WIDTH, GAME_TITLE, TARGET_FPS}; +use crate::networking::message_handler; +use crate::screens::draw_screens; + +mod commands; +mod input_handler; +mod map_handler; +mod new_player; +mod updates_handler; pub(crate) fn run( sender: Sender, @@ -20,11 +22,11 @@ pub(crate) fn run( client_addr: &str, player_name: &str, ) { + //setup message handling threads let (player_update_sender, player_update_receiver) = crossbeam_channel::unbounded(); let (entity_update_sender, entity_update_receiver) = crossbeam_channel::unbounded(); debug!("Spawned thread channels."); let local_sender = sender.clone(); - message_handler::spawn_message_handler_thread( sender, receiver, @@ -32,9 +34,11 @@ pub(crate) fn run( entity_update_sender, ); + //get basic data from server needed to start game let all_maps = map_handler::request_all_maps_data(&local_sender, &server_addr, &player_update_receiver); + //create player let mut player = new_player::send_new_player_request( &local_sender, player_name, @@ -43,10 +47,9 @@ pub(crate) fn run( &player_update_receiver, ); - let mut viewport = Viewport::new(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, TARGET_FPS); - + //initialise console engine let mut console = - console_engine::ConsoleEngine::init(viewport.width, viewport.height, viewport.target_fps); + console_engine::ConsoleEngine::init(CONSOLE_WIDTH, CONSOLE_HEIGHT, TARGET_FPS); console.set_title(GAME_TITLE); info!("Initialised console engine."); @@ -55,8 +58,11 @@ pub(crate) fn run( display_details: HashMap::new(), }; + let mut status_messages: Vec = vec![]; + info!("Starting game loop"); loop { + //wait for target fps, and clear screen between frames console.wait_frame(); console.clear_screen(); @@ -71,19 +77,24 @@ pub(crate) fn run( other_entities, ); - viewport.draw_viewport_contents( + input_handler::handle_other_input( + &mut console, + &mut status_messages, + &player, + &all_maps, + &other_entities, + ); + + //update and redraw the screens + draw_screens( &mut console, + &all_maps, &player, - all_maps.get(&player.position.map).unwrap_or_else(|| { - error!( - "There is no map for current player position: {}", - &player.position.map - ); - process::exit(1); - }), &other_entities, + &status_messages, ); + //check if we should quit if should_quit(&console) { info!("Ctrl-q detected - quitting app."); break; diff --git a/rustyhack_client/src/game/commands.rs b/rustyhack_client/src/game/commands.rs new file mode 100644 index 0000000..e7563a5 --- /dev/null +++ b/rustyhack_client/src/game/commands.rs @@ -0,0 +1 @@ +pub(crate) mod look_command; diff --git a/rustyhack_client/src/game/commands/look_command.rs b/rustyhack_client/src/game/commands/look_command.rs new file mode 100644 index 0000000..3879035 --- /dev/null +++ b/rustyhack_client/src/game/commands/look_command.rs @@ -0,0 +1,81 @@ +use chrono::{DateTime, Local}; +use rustyhack_lib::background_map::AllMaps; +use rustyhack_lib::ecs::player::Player; +use rustyhack_lib::message_handler::player_message::EntityUpdates; + +pub(crate) fn get_what_player_sees( + status_messages: &mut Vec, + player: &Player, + all_maps: &AllMaps, + other_entities: &EntityUpdates, +) { + let date_time: DateTime = Local::now(); + let time = date_time.format("[%H:%M:%S] ").to_string(); + let current_map = all_maps.get(&player.position.map).unwrap(); + + let underneath = + current_map.data[player.position.y as usize][player.position.x as usize].to_string(); + let mut north = + current_map.data[(player.position.y - 1) as usize][player.position.x as usize].to_string(); + let mut south = + current_map.data[(player.position.y + 1) as usize][player.position.x as usize].to_string(); + let mut east = + current_map.data[player.position.y as usize][(player.position.x + 1) as usize].to_string(); + let mut west = + current_map.data[player.position.y as usize][(player.position.x - 1) as usize].to_string(); + + north = return_visible_entity_at( + north, + other_entities, + player, + player.position.x, + player.position.y - 1, + ); + south = return_visible_entity_at( + south, + other_entities, + player, + player.position.x, + player.position.y + 1, + ); + east = return_visible_entity_at( + east, + other_entities, + player, + player.position.x + 1, + player.position.y, + ); + west = return_visible_entity_at( + west, + other_entities, + player, + player.position.x - 1, + player.position.y, + ); + + status_messages.push(time.to_owned() + "You see..."); + status_messages.push(time.to_owned() + "Underneath: " + &*underneath); + status_messages.push(time.to_owned() + "North: " + &*north); + status_messages.push(time.to_owned() + "South: " + &*south); + status_messages.push(time.to_owned() + "East: " + &*east); + status_messages.push(time + "West: " + &*west); +} + +fn return_visible_entity_at( + mut text: String, + other_entities: &EntityUpdates, + player: &Player, + x: i32, + y: i32, +) -> String { + for (name, position) in other_entities.position_updates.clone() { + if name != player.player_details.player_name + && position.map == player.position.map + && position.x == x + && position.y == y + { + text = name; + } + } + text +} diff --git a/rustyhack_client/src/game/input_handler.rs b/rustyhack_client/src/game/input_handler.rs new file mode 100644 index 0000000..6ddb842 --- /dev/null +++ b/rustyhack_client/src/game/input_handler.rs @@ -0,0 +1,22 @@ +use crate::game::commands; +use console_engine::{ConsoleEngine, KeyCode}; +use rustyhack_lib::background_map::AllMaps; +use rustyhack_lib::ecs::player::Player; +use rustyhack_lib::message_handler::player_message::EntityUpdates; + +pub(crate) fn handle_other_input( + console: &mut ConsoleEngine, + status_messages: &mut Vec, + player: &Player, + all_maps: &AllMaps, + other_entities: &EntityUpdates, +) { + if console.is_key_pressed(KeyCode::Char(' ')) { + commands::look_command::get_what_player_sees( + status_messages, + player, + all_maps, + other_entities, + ); + } +} diff --git a/rustyhack_client/src/main.rs b/rustyhack_client/src/main.rs index 811b037..91e4ce9 100644 --- a/rustyhack_client/src/main.rs +++ b/rustyhack_client/src/main.rs @@ -3,6 +3,7 @@ use std::env; mod consts; mod game; mod networking; +mod screens; mod setup; #[macro_use] diff --git a/rustyhack_client/src/screens.rs b/rustyhack_client/src/screens.rs new file mode 100644 index 0000000..ac06d8e --- /dev/null +++ b/rustyhack_client/src/screens.rs @@ -0,0 +1,43 @@ +use crate::consts; +use console_engine::ConsoleEngine; +use rustyhack_lib::background_map::AllMaps; +use rustyhack_lib::ecs::player::Player; +use rustyhack_lib::message_handler::player_message::EntityUpdates; +use std::process; + +mod bottom_text_window; +mod side_status_bar; +mod top_status_bar; +pub(crate) mod viewport; + +pub(crate) fn draw_screens( + console: &mut ConsoleEngine, + all_maps: &AllMaps, + player: &Player, + other_entities: &EntityUpdates, + status_messages: &[String], +) { + //update the player viewport contents + let viewport = viewport::draw_viewport_contents( + &player, + all_maps.get(&player.position.map).unwrap_or_else(|| { + error!( + "There is no map for current player position: {}", + &player.position.map + ); + process::exit(1); + }), + &other_entities, + ); + + let top_status_bar = top_status_bar::draw(player); + let side_status_bar = side_status_bar::draw(player); + let bottom_text_window = bottom_text_window::draw(status_messages); + + //final draw step + console.print_screen(0, 0, &top_status_bar); + console.print_screen(consts::VIEWPORT_WIDTH as i32, 1, &side_status_bar); + console.print_screen(0, consts::VIEWPORT_HEIGHT as i32, &bottom_text_window); + console.print_screen(0, 1, &viewport); + console.draw(); +} diff --git a/rustyhack_client/src/screens/bottom_text_window.rs b/rustyhack_client/src/screens/bottom_text_window.rs new file mode 100644 index 0000000..186a7e0 --- /dev/null +++ b/rustyhack_client/src/screens/bottom_text_window.rs @@ -0,0 +1,19 @@ +use crate::consts; +use console_engine::screen::Screen; + +pub(crate) fn draw(status_messages: &[String]) -> Screen { + let mut screen = Screen::new( + consts::CONSOLE_WIDTH, + consts::CONSOLE_HEIGHT - consts::VIEWPORT_HEIGHT, + ); + if !status_messages.is_empty() { + for (count, message) in status_messages.iter().rev().enumerate() { + if (count as u32) < screen.get_height() { + screen.print(0, (screen.get_height() - 1 - count as u32) as i32, message); + } else { + break; + } + } + } + screen +} diff --git a/rustyhack_client/src/screens/side_status_bar.rs b/rustyhack_client/src/screens/side_status_bar.rs new file mode 100644 index 0000000..5da85a3 --- /dev/null +++ b/rustyhack_client/src/screens/side_status_bar.rs @@ -0,0 +1,33 @@ +use crate::consts; +use console_engine::screen::Screen; +use rustyhack_lib::ecs::player::Player; + +pub(crate) fn draw(player: &Player) -> Screen { + let mut screen = Screen::new( + consts::CONSOLE_WIDTH - consts::VIEWPORT_WIDTH, + consts::VIEWPORT_HEIGHT, + ); + + let lvl_string = "Lvl: ".to_owned() + &player.player_details.level.to_string(); + let exp_string = "Exp: ".to_owned() + &player.player_details.exp.to_string(); + let hp_string = "HP: ".to_owned() + + &player.stats.current_hp.to_string() + + "/" + + &player.stats.max_hp.to_string(); + let armour_string = "Armour: ".to_owned() + &player.stats.armour.to_string() + "%"; + let str_string = "Str: ".to_owned() + &player.stats.str.to_string(); + let dex_string = "Dex: ".to_owned() + &player.stats.dex.to_string(); + let con_string = "Con: ".to_owned() + &player.stats.con.to_string(); + let gold_string = "Gold: ".to_owned() + &player.player_details.gold.to_string(); + + screen.print(1, 0, &player.player_details.player_name); + screen.print(1, 1, &lvl_string); + screen.print(1, 2, &exp_string); + screen.print(1, 4, &hp_string); + screen.print(1, 5, &armour_string); + screen.print(1, 6, &str_string); + screen.print(1, 7, &dex_string); + screen.print(1, 8, &con_string); + screen.print(1, 10, &gold_string); + screen +} diff --git a/rustyhack_client/src/screens/top_status_bar.rs b/rustyhack_client/src/screens/top_status_bar.rs new file mode 100644 index 0000000..0b801dc --- /dev/null +++ b/rustyhack_client/src/screens/top_status_bar.rs @@ -0,0 +1,17 @@ +use crate::consts; +use console_engine::pixel; +use console_engine::screen::Screen; +use rustyhack_lib::ecs::player::Player; + +pub(crate) fn draw(player: &Player) -> Screen { + let mut screen = Screen::new(consts::CONSOLE_WIDTH, 1); + screen.line(0, 0, (consts::CONSOLE_WIDTH - 1) as i32, 0, pixel::pxl('=')); + let player_update_text = player.position.map.clone() + + " (" + + &player.position.x.to_string() + + "," + + &player.position.y.to_string() + + ")"; + screen.print((consts::CONSOLE_WIDTH / 4) as i32, 0, &player_update_text); + screen +} diff --git a/rustyhack_client/src/game/viewport.rs b/rustyhack_client/src/screens/viewport.rs similarity index 73% rename from rustyhack_client/src/game/viewport.rs rename to rustyhack_client/src/screens/viewport.rs index ad4a089..687daf1 100644 --- a/rustyhack_client/src/game/viewport.rs +++ b/rustyhack_client/src/screens/viewport.rs @@ -1,46 +1,46 @@ -use console_engine::{pixel, ConsoleEngine}; +use crate::consts; +use console_engine::pixel; +use console_engine::screen::Screen; use rustyhack_lib::background_map::tiles::{Tile, TilePosition}; use rustyhack_lib::background_map::BackgroundMap; use rustyhack_lib::ecs::components::DisplayDetails; use rustyhack_lib::ecs::player::Player; use rustyhack_lib::message_handler::player_message::EntityUpdates; -pub(crate) struct Viewport { - pub(crate) width: u32, - pub(crate) height: u32, - pub(crate) target_fps: u32, - pub(crate) viewable_map_topleft: TilePosition, +struct Viewport { + width: u32, + height: u32, + viewable_map_topleft: TilePosition, } -impl Viewport { - pub(crate) fn new(width: u32, height: u32, target_fps: u32) -> Viewport { +impl Default for Viewport { + fn default() -> Self { Viewport { - width, - height, - target_fps, + width: consts::VIEWPORT_WIDTH, + height: consts::VIEWPORT_HEIGHT, viewable_map_topleft: TilePosition { x: 0, y: 0 }, } } +} - pub(crate) fn draw_viewport_contents( - &mut self, - console: &mut ConsoleEngine, - player: &Player, - background_map: &BackgroundMap, - entity_updates: &EntityUpdates, - ) { - calculate_viewable_map_coords(self, &player); - draw_viewable_map(console, &background_map, &self); - draw_viewport_frame(console, &self); - draw_player(console, &self, &player); - draw_other_entities(console, &player, &entity_updates, &self); - console.draw(); - } +pub(crate) fn draw_viewport_contents( + player: &Player, + background_map: &BackgroundMap, + entity_updates: &EntityUpdates, +) -> Screen { + let mut viewport = Viewport::default(); + let mut screen = Screen::new(viewport.width, viewport.height); + calculate_viewable_map_coords(&mut viewport, &player); + draw_viewable_map(&mut screen, &background_map, &viewport); + draw_viewport_frame(&mut screen, &viewport); + draw_player(&mut screen, &viewport, &player); + draw_other_entities(&mut screen, &player, &entity_updates, &viewport); + screen } -fn draw_player(console: &mut ConsoleEngine, viewport: &Viewport, player: &Player) { +fn draw_player(screen: &mut Screen, viewport: &Viewport, player: &Player) { debug!("Drawing player."); - console.set_pxl( + screen.set_pxl( (viewport.width / 2) as i32, (viewport.height / 2) as i32, pixel::pxl_fg(player.display_details.icon, player.display_details.colour), @@ -48,7 +48,7 @@ fn draw_player(console: &mut ConsoleEngine, viewport: &Viewport, player: &Player } fn draw_other_entities( - console: &mut ConsoleEngine, + screen: &mut Screen, player: &Player, entity_updates: &EntityUpdates, viewport: &Viewport, @@ -57,7 +57,7 @@ fn draw_other_entities( let default_display_details = DisplayDetails::default(); let updates = entity_updates.position_updates.clone(); for (name, position) in updates { - if name != player.player_details.player_name { + if name != player.player_details.player_name && position.map == player.position.map { let relative_entity_position = TilePosition { x: position.x - viewport.viewable_map_topleft.x, y: position.y - viewport.viewable_map_topleft.y, @@ -72,7 +72,7 @@ fn draw_other_entities( warn!("Entity update for {} doesn't have a corresponding display detail, using default.", &name); &default_display_details}); - console.set_pxl( + screen.set_pxl( relative_entity_position.x, relative_entity_position.y, pixel::pxl_fg(display_details.icon, display_details.colour), @@ -92,7 +92,7 @@ fn calculate_viewable_map_coords(viewport: &mut Viewport, player: &Player) { } } -fn draw_viewable_map(console: &mut ConsoleEngine, world_map: &BackgroundMap, viewport: &Viewport) { +fn draw_viewable_map(screen: &mut Screen, world_map: &BackgroundMap, viewport: &Viewport) { debug!("Drawing viewable map."); let mut viewport_print_y_loc: i32 = 0; while viewport_print_y_loc < viewport.height as i32 { @@ -112,7 +112,7 @@ fn draw_viewable_map(console: &mut ConsoleEngine, world_map: &BackgroundMap, vie .len() as i32) && (current_map_print_loc.y < world_map.data().len() as i32) { - console.print( + screen.print( viewport_print_x_loc, viewport_print_y_loc, &world_map @@ -125,7 +125,7 @@ fn draw_viewable_map(console: &mut ConsoleEngine, world_map: &BackgroundMap, vie .to_string(), ); } else { - console.print(viewport_print_x_loc, viewport_print_y_loc, " "); + screen.print(viewport_print_x_loc, viewport_print_y_loc, " "); } viewport_print_x_loc += 1; } @@ -133,9 +133,9 @@ fn draw_viewable_map(console: &mut ConsoleEngine, world_map: &BackgroundMap, vie } } -fn draw_viewport_frame(console: &mut ConsoleEngine, viewport: &Viewport) { +fn draw_viewport_frame(screen: &mut Screen, viewport: &Viewport) { debug!("Drawing viewport frame."); - console.rect( + screen.rect( 0, 0, (viewport.width - 1) as i32, diff --git a/rustyhack_client/src/setup.rs b/rustyhack_client/src/setup.rs index 29930c0..ad21e7d 100644 --- a/rustyhack_client/src/setup.rs +++ b/rustyhack_client/src/setup.rs @@ -1,9 +1,10 @@ use crate::consts; use regex::Regex; +use rustyhack_lib::file_utils; use simplelog::{CombinedLogger, LevelFilter, TermLogger, TerminalMode, WriteLogger}; use std::fs::File; use std::net::SocketAddr; -use std::{env, io, process}; +use std::{io, process}; pub(crate) fn initialise_log(args: Vec) { let mut log_level = LevelFilter::Info; @@ -12,10 +13,7 @@ pub(crate) fn initialise_log(args: Vec) { log_level = LevelFilter::Debug; } - let mut file_location = env::current_exe().unwrap_or_else(|err| { - eprintln!("Problem getting current executable location: {}", err); - process::exit(1); - }); + let mut file_location = file_utils::current_exe_location(); file_location.pop(); file_location.push(consts::LOG_NAME); CombinedLogger::init(vec![ diff --git a/rustyhack_lib/Cargo.toml b/rustyhack_lib/Cargo.toml index c802dc0..2a85d94 100644 --- a/rustyhack_lib/Cargo.toml +++ b/rustyhack_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustyhack_lib" -version = "0.1.1" +version = "0.2.0" authors = ["pbellchambers "] edition = "2018" repository = "https://github.com/pbellchambers/rustyhack-mmo" @@ -9,5 +9,10 @@ license = "AGPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "1.0.123" -console_engine = "1.5.0" \ No newline at end of file +log = "0.4.14" +simplelog = "0.9.0" +serde = { version = "1.0.123", features = ["derive"] } +console_engine = "1.5.0" +uuid = { version = "0.8.2", features = ["serde", "v4"] } +strum = "0.20.0" +strum_macros = "0.20.1" \ No newline at end of file diff --git a/rustyhack_lib/src/background_map/character_map.rs b/rustyhack_lib/src/background_map/character_map.rs index 235b726..1c501a1 100644 --- a/rustyhack_lib/src/background_map/character_map.rs +++ b/rustyhack_lib/src/background_map/character_map.rs @@ -9,6 +9,8 @@ pub fn map_character_to_tile(x: i32, y: i32, character: char) -> Tile { '%' => Tile::EndOfFile, '#' => Tile::Boundary, ' ' => Tile::EmptySpace, + '^' => Tile::UpLadder, + 'v' => Tile::DownLadder, '|' => Tile::Wall(Wall::new(x, y, character)), '-' => Tile::Wall(Wall::new(x, y, character)), '+' => Tile::Door(Door::new(x, y, OpenState::Closed)), diff --git a/rustyhack_lib/src/background_map/tiles.rs b/rustyhack_lib/src/background_map/tiles.rs index a690944..31ed6b0 100644 --- a/rustyhack_lib/src/background_map/tiles.rs +++ b/rustyhack_lib/src/background_map/tiles.rs @@ -1,9 +1,9 @@ pub mod door; pub mod wall; - use crate::background_map::tiles::door::Door; use crate::background_map::tiles::wall::Wall; use serde::{Deserialize, Serialize}; +use strum_macros::Display; #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum Collidable { @@ -23,10 +23,12 @@ pub struct TilePosition { pub y: i32, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Display, Clone, Copy, Debug, Serialize, Deserialize)] pub enum Tile { Wall(Wall), Door(Door), + UpLadder, + DownLadder, EmptySpace, Boundary, NewLine, @@ -39,6 +41,8 @@ impl Tile { match self { Tile::Wall(wall) => wall.character_icon, Tile::Door(door) => door.character_icon, + Tile::UpLadder => '^', + Tile::DownLadder => 'v', Tile::EmptySpace => ' ', Tile::Boundary => '#', Tile::NewLine => ' ', diff --git a/rustyhack_lib/src/consts.rs b/rustyhack_lib/src/consts.rs index 1e8adcb..f30ba73 100644 --- a/rustyhack_lib/src/consts.rs +++ b/rustyhack_lib/src/consts.rs @@ -1,7 +1,12 @@ use console_engine::Color; -pub const DEFAULT_MAP: &str = "default.txt"; +pub const DEFAULT_MAP: &str = "Home"; pub const DEFAULT_PLAYER_ICON: char = '@'; pub const DEFAULT_PLAYER_COLOUR: Color = Color::Magenta; -pub const DEFAULT_PLAYER_POSITION_X: i32 = 5; -pub const DEFAULT_PLAYER_POSITION_Y: i32 = 5; +pub const DEFAULT_PLAYER_POSITION_X: i32 = 16; +pub const DEFAULT_PLAYER_POSITION_Y: i32 = 6; +pub const DEFAULT_MONSTER_TYPE: &str = "default_monster"; +pub const DEFAULT_MONSTER_ICON: char = 'x'; +pub const DEFAULT_MONSTER_COLOUR: Color = Color::Red; +pub const DEFAULT_MONSTER_POSITION_X: i32 = 20; +pub const DEFAULT_MONSTER_POSITION_Y: i32 = 20; diff --git a/rustyhack_lib/src/ecs.rs b/rustyhack_lib/src/ecs.rs index 623f80a..334d87e 100644 --- a/rustyhack_lib/src/ecs.rs +++ b/rustyhack_lib/src/ecs.rs @@ -1,2 +1,3 @@ pub mod components; +pub mod monster; pub mod player; diff --git a/rustyhack_lib/src/ecs/components.rs b/rustyhack_lib/src/ecs/components.rs index e5ed51f..a832907 100644 --- a/rustyhack_lib/src/ecs/components.rs +++ b/rustyhack_lib/src/ecs/components.rs @@ -1,6 +1,15 @@ use crate::consts::{DEFAULT_PLAYER_COLOUR, DEFAULT_PLAYER_ICON}; +use crate::ecs::monster::Monster; +use crate::ecs::player::Player; use console_engine::Color; use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum EntityType { + Monster(Monster), + Player(Player), +} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Position { @@ -44,4 +53,28 @@ pub struct PlayerDetails { pub player_name: String, pub client_addr: String, pub currently_online: bool, + pub level: u32, + pub exp: u32, + pub gold: u32, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MonsterDetails { + pub id: Uuid, + pub monster_type: String, + pub spawn_position: Position, + pub is_active: bool, + pub current_target: Option, + pub exp: u32, + pub gold: u32, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct Stats { + pub current_hp: u32, + pub max_hp: u32, + pub str: u32, + pub dex: u32, + pub con: u32, + pub armour: u32, } diff --git a/rustyhack_lib/src/ecs/monster.rs b/rustyhack_lib/src/ecs/monster.rs new file mode 100644 index 0000000..002d979 --- /dev/null +++ b/rustyhack_lib/src/ecs/monster.rs @@ -0,0 +1,58 @@ +use crate::consts::{ + DEFAULT_MAP, DEFAULT_MONSTER_COLOUR, DEFAULT_MONSTER_ICON, DEFAULT_MONSTER_POSITION_X, + DEFAULT_MONSTER_POSITION_Y, DEFAULT_MONSTER_TYPE, +}; +use crate::ecs::components::{DisplayDetails, MonsterDetails, Position, Stats, Velocity}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use uuid::Uuid; + +pub type AllMonsterDefinitions = HashMap; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Monster { + pub monster_details: MonsterDetails, + pub display_details: DisplayDetails, + pub position: Position, + pub velocity: Velocity, + pub stats: Stats, +} + +impl Default for Monster { + fn default() -> Self { + Monster { + monster_details: MonsterDetails { + id: Uuid::new_v4(), + monster_type: DEFAULT_MONSTER_TYPE.to_string(), + spawn_position: Position { + x: DEFAULT_MONSTER_POSITION_X, + y: DEFAULT_MONSTER_POSITION_Y, + map: DEFAULT_MAP.to_string(), + }, + is_active: false, + current_target: None, + exp: 1, + gold: 1, + }, + display_details: DisplayDetails { + icon: DEFAULT_MONSTER_ICON, + colour: DEFAULT_MONSTER_COLOUR, + ..Default::default() + }, + position: Position { + x: DEFAULT_MONSTER_POSITION_X, + y: DEFAULT_MONSTER_POSITION_Y, + map: DEFAULT_MAP.to_string(), + }, + velocity: Velocity { x: 0, y: 0 }, + stats: Stats { + current_hp: 1, + max_hp: 1, + str: 1, + dex: 1, + con: 1, + armour: 1, + }, + } + } +} diff --git a/rustyhack_lib/src/ecs/player.rs b/rustyhack_lib/src/ecs/player.rs index 403190b..57c7b83 100644 --- a/rustyhack_lib/src/ecs/player.rs +++ b/rustyhack_lib/src/ecs/player.rs @@ -1,5 +1,5 @@ use crate::consts::{DEFAULT_MAP, DEFAULT_PLAYER_POSITION_X, DEFAULT_PLAYER_POSITION_Y}; -use crate::ecs::components::{DisplayDetails, PlayerDetails, Position}; +use crate::ecs::components::{DisplayDetails, PlayerDetails, Position, Stats}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -7,6 +7,7 @@ pub struct Player { pub player_details: PlayerDetails, pub display_details: DisplayDetails, pub position: Position, + pub stats: Stats, } impl Default for Player { @@ -16,6 +17,9 @@ impl Default for Player { player_name: "".to_string(), client_addr: "".to_string(), currently_online: false, + level: 1, + exp: 0, + gold: 0, }, display_details: DisplayDetails::default(), position: Position { @@ -23,6 +27,14 @@ impl Default for Player { y: DEFAULT_PLAYER_POSITION_Y, map: DEFAULT_MAP.to_string(), }, + stats: Stats { + current_hp: 50, + max_hp: 50, + str: 10, + dex: 10, + con: 10, + armour: 5, + }, } } } diff --git a/rustyhack_lib/src/file_utils.rs b/rustyhack_lib/src/file_utils.rs new file mode 100644 index 0000000..ac9d9b9 --- /dev/null +++ b/rustyhack_lib/src/file_utils.rs @@ -0,0 +1,21 @@ +use std::fs::ReadDir; +use std::path::PathBuf; +use std::{env, fs, process}; + +pub fn current_exe_location() -> PathBuf { + env::current_exe().unwrap_or_else(|err| { + error!("Problem getting current executable location: {}", err); + process::exit(1); + }) +} + +pub fn get_all_files_in_location(path: PathBuf) -> ReadDir { + fs::read_dir(path.as_path()).unwrap_or_else(|err| { + error!( + "Problem reading directory {:?}, error: {}", + path.as_path(), + err + ); + process::exit(1); + }) +} diff --git a/rustyhack_lib/src/lib.rs b/rustyhack_lib/src/lib.rs index ebaa094..a01acd5 100644 --- a/rustyhack_lib/src/lib.rs +++ b/rustyhack_lib/src/lib.rs @@ -1,4 +1,9 @@ pub mod background_map; pub mod consts; pub mod ecs; +pub mod file_utils; pub mod message_handler; + +#[macro_use] +extern crate log; +extern crate simplelog; diff --git a/rustyhack_server/Cargo.toml b/rustyhack_server/Cargo.toml index f5e1269..1b3bb0c 100644 --- a/rustyhack_server/Cargo.toml +++ b/rustyhack_server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustyhack_server" -version = "0.1.1" +version = "0.2.0" authors = ["pbellchambers "] edition = "2018" repository = "https://github.com/pbellchambers/rustyhack-mmo" @@ -14,8 +14,11 @@ log = "0.4.14" simplelog = "0.9.0" console_engine = "1.5.0" crossterm = { version = "0.19.0", features = ["serde"] } -legion = "0.3.1" +legion = "0.4.0" laminar = "0.4.0" crossbeam-channel = "0.4.4" -serde = "1.0.123" -bincode = "1.3.1" \ No newline at end of file +serde = { version = "1.0.123", features = ["derive"] } +serde_json = "1.0.62" +bincode = "1.3.1" +rand = "0.8.3" +uuid = { version = "0.8.2", features = ["serde", "v4"] } \ No newline at end of file diff --git a/rustyhack_server/src/consts.rs b/rustyhack_server/src/consts.rs index 50c0630..b7858a7 100644 --- a/rustyhack_server/src/consts.rs +++ b/rustyhack_server/src/consts.rs @@ -2,4 +2,10 @@ use std::time::Duration; pub(crate) const LOG_NAME: &str = "rustyhack_server.log"; pub(crate) const ENTITY_UPDATE_TICK: Duration = Duration::from_millis(50); +pub(crate) const MONSTER_UPDATE_TICK: Duration = Duration::from_millis(300); pub(crate) const LOOP_TICK: Duration = Duration::from_millis(10); +pub(crate) const MONSTER_DISTANCE_ACTIVATION: i32 = 10; +pub(crate) const ASSETS_DIRECTORY: &str = "assets"; +pub(crate) const MAPS_DIRECTORY: &str = "maps"; +pub(crate) const MONSTERS_DIRECTORY: &str = "monsters"; +pub(crate) const SPAWNS_DIRECTORY: &str = "spawns"; diff --git a/rustyhack_server/src/game.rs b/rustyhack_server/src/game.rs index 4c13c82..7ae2844 100644 --- a/rustyhack_server/src/game.rs +++ b/rustyhack_server/src/game.rs @@ -12,31 +12,46 @@ use crate::consts; use crate::networking::message_handler; mod background_map; +mod map_state; +mod monsters; mod player_updates; +mod players; +mod spawns; mod systems; pub(crate) fn run(sender: Sender, receiver: Receiver) { - let all_maps_resource = background_map::initialise_all_maps(); + //initialise all basic resources let all_maps = background_map::initialise_all_maps(); + let all_maps_resource = all_maps.clone(); + let all_map_states = map_state::initialise_all_map_states(&all_maps); + let all_monster_definitions = monsters::initialise_all_monster_definitions(); + let all_spawns = spawns::initialise_all_spawn_definitions(); + let mut player_velocity_updates: HashMap = HashMap::new(); + let mut world = World::default(); + info!("Initialised ECS World"); + let mut player_update_schedule = systems::build_player_update_schedule(); + let mut monster_update_schedule = systems::build_monster_update_schedule(); + let mut map_state_update_schedule = systems::build_map_state_update_schedule(); + //initialise message handler thread let (channel_sender, channel_receiver) = crossbeam_channel::unbounded(); info!("Created thread channel sender and receiver."); let local_sender = sender.clone(); - message_handler::spawn_message_handler_thread(sender, receiver, all_maps, channel_sender); - let mut world = World::default(); - info!("Initialised ECS World"); - - let mut schedule = systems::build_schedule(); - + //load resources into world let mut resources = Resources::default(); resources.insert(all_maps_resource); - info!("Finished loading all_maps into world resources."); + resources.insert(all_map_states); + info!("Finished loading resources into world."); - let mut player_velocity_updates: HashMap = HashMap::new(); + //spawn initial monsters + monsters::spawn_initial_monsters(&mut world, &all_monster_definitions, &all_spawns); + info!("Spawned all monsters in initial positions."); + //start tick counts let mut entity_tick_time = Instant::now(); + let mut monster_tick_time = Instant::now(); let mut loop_tick_time = Instant::now(); info!("Starting game loop"); loop { @@ -52,9 +67,10 @@ pub(crate) fn run(sender: Sender, receiver: Receiver) { resources.insert(player_velocity_updates.to_owned()); debug!("Added player velocity updates to world resources."); - debug!("Executing schedule..."); - schedule.execute(&mut world, &mut resources); - debug!("Schedule executed successfully."); + debug!("Executing player update schedule..."); + map_state_update_schedule.execute(&mut world, &mut resources); + player_update_schedule.execute(&mut world, &mut resources); + debug!("Player update schedule executed successfully."); player_velocity_updates = player_updates::send_player_updates( &mut world, @@ -63,9 +79,18 @@ pub(crate) fn run(sender: Sender, receiver: Receiver) { ); } - //do every 50ms + if monster_tick_time.elapsed() > consts::MONSTER_UPDATE_TICK { + monsters::update_velocities(&mut world); + monster_tick_time = Instant::now(); + + debug!("Executing monster update schedule..."); + map_state_update_schedule.execute(&mut world, &mut resources); + monster_update_schedule.execute(&mut world, &mut resources); + debug!("Monster update schedule executed successfully."); + } + if entity_tick_time.elapsed() > consts::ENTITY_UPDATE_TICK { - player_updates::send_other_entities_updates(&mut world, &local_sender); + player_updates::send_other_entities_updates(&world, &local_sender); entity_tick_time = Instant::now(); } diff --git a/rustyhack_server/src/game/background_map.rs b/rustyhack_server/src/game/background_map.rs index 8383e1a..b3c42b9 100644 --- a/rustyhack_server/src/game/background_map.rs +++ b/rustyhack_server/src/game/background_map.rs @@ -1,18 +1,27 @@ +use crate::consts; use rustyhack_lib::background_map::tiles::Tile; use rustyhack_lib::background_map::AllMaps; use rustyhack_lib::background_map::{character_map, BackgroundMap}; +use rustyhack_lib::file_utils; use std::collections::HashMap; use std::path::PathBuf; -use std::{env, fs, process}; +use std::{fs, process}; pub(crate) fn initialise_all_maps() -> AllMaps { info!("About to initialise all maps"); let mut all_maps: AllMaps = HashMap::new(); - let file_location = get_maps_directory_location(); - let paths = fs::read_dir(file_location.as_path()).unwrap(); + let paths = file_utils::get_all_files_in_location(maps_directory_location()); for path in paths { let unwrapped_path = path.unwrap(); - let filename = String::from(unwrapped_path.file_name().to_str().unwrap()); + let filename = String::from( + unwrapped_path + .file_name() + .to_str() + .unwrap() + .split('.') + .next() + .unwrap(), + ); let map = initialise_map(&unwrapped_path.path()); info!("Initialised map: {:?}", &filename); all_maps.insert(filename, map); @@ -21,14 +30,11 @@ pub(crate) fn initialise_all_maps() -> AllMaps { all_maps } -fn get_maps_directory_location() -> PathBuf { - let mut file_location = env::current_exe().unwrap_or_else(|err| { - error!("Problem getting current executable location: {}", err); - process::exit(1); - }); +fn maps_directory_location() -> PathBuf { + let mut file_location = file_utils::current_exe_location(); file_location.pop(); - file_location.push("assets"); - file_location.push("maps"); + file_location.push(consts::ASSETS_DIRECTORY); + file_location.push(consts::MAPS_DIRECTORY); file_location } diff --git a/rustyhack_server/src/game/map_state.rs b/rustyhack_server/src/game/map_state.rs new file mode 100644 index 0000000..788cff2 --- /dev/null +++ b/rustyhack_server/src/game/map_state.rs @@ -0,0 +1,56 @@ +use rustyhack_lib::background_map::AllMaps; +use rustyhack_lib::ecs::components::EntityType; +use std::collections::HashMap; + +pub(crate) type MapState = Vec>>; + +pub(crate) type AllMapStates = HashMap; + +pub(crate) fn initialise_all_map_states(all_maps: &AllMaps) -> AllMapStates { + info!("About to initialise empty map state for all maps"); + let mut all_map_states: AllMapStates = HashMap::new(); + for (map_name, background_map) in all_maps { + let mut map_state: MapState = vec![vec![vec![]]]; + for row in background_map.data.iter() { + let mut row_vec: Vec> = vec![vec![]]; + for _tile in row.iter() { + //push an empty map state vector for each tile + row_vec.push(vec![]); + } + map_state.push(row_vec); + } + info!("Initialised map state for {} map.", &map_name); + all_map_states.insert(map_name.clone(), map_state); + } + info!("Finished initialising all map states."); + all_map_states +} + +pub(crate) fn insert_entity_at(map: &mut MapState, entity: EntityType, x: usize, y: usize) { + map[y][x].push(entity); +} + +pub(crate) fn clear_all_entities(map_states: &mut AllMapStates) -> &mut AllMapStates { + for map_state in &mut map_states.values_mut() { + for map_row in map_state.iter_mut() { + for map_tile in map_row.iter_mut() { + map_tile.clear(); + } + } + } + map_states +} + +pub(crate) fn is_colliding(x: usize, y: usize, map_state: &mut MapState) -> bool { + let mut colliding = false; + for entity_type in map_state[y][x].iter() { + match entity_type { + EntityType::Monster(monster) => colliding = monster.display_details.collidable, + EntityType::Player(player) => { + colliding = + player.player_details.currently_online && player.display_details.collidable + } + } + } + colliding +} diff --git a/rustyhack_server/src/game/monsters.rs b/rustyhack_server/src/game/monsters.rs new file mode 100644 index 0000000..83897d2 --- /dev/null +++ b/rustyhack_server/src/game/monsters.rs @@ -0,0 +1,217 @@ +use crate::consts; +use crate::consts::MONSTER_DISTANCE_ACTIVATION; +use crate::game::players; +use crate::game::spawns::AllSpawns; +use legion::{IntoQuery, World}; +use rand::Rng; +use rustyhack_lib::ecs::components::{DisplayDetails, MonsterDetails, Position, Stats, Velocity}; +use rustyhack_lib::ecs::monster::{AllMonsterDefinitions, Monster}; +use rustyhack_lib::file_utils; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; +use std::process; +use uuid::Uuid; + +pub(crate) fn initialise_all_monster_definitions() -> AllMonsterDefinitions { + info!("About to initialise all monster definitions"); + let mut all_monster_definitions: AllMonsterDefinitions = HashMap::new(); + let paths = file_utils::get_all_files_in_location(monsters_directory_location()); + for path in paths { + let unwrapped_path = path.unwrap(); + let name = String::from( + unwrapped_path + .file_name() + .to_str() + .unwrap() + .split('.') + .next() + .unwrap(), + ); + let monster: Monster = get_monster_definition_from_path(&unwrapped_path.path()); + info!("Initialised monster: {:?}", &name); + all_monster_definitions.insert(name, monster); + } + all_monster_definitions +} + +fn monsters_directory_location() -> PathBuf { + let mut file_location = file_utils::current_exe_location(); + file_location.pop(); + file_location.push(consts::ASSETS_DIRECTORY); + file_location.push(consts::MONSTERS_DIRECTORY); + file_location +} + +fn get_monster_definition_from_path(path: &PathBuf) -> Monster { + let file = File::open(path).unwrap_or_else(|err| { + error!( + "Problem getting monster definition from file: {:?}, error: {}", + path, err + ); + process::exit(1); + }); + let buf_reader = BufReader::new(file); + serde_json::from_reader(buf_reader).unwrap_or_else(|err| { + error!( + "Problem deserialising monster definition from file: {:?}, error: {}", + path, err + ); + process::exit(1); + }) +} + +pub(crate) fn spawn_initial_monsters( + world: &mut World, + all_monster_definitions: &AllMonsterDefinitions, + all_spawns: &AllSpawns, +) { + info!("Spawning initial monsters."); + let mut monsters_vec: Vec<(MonsterDetails, DisplayDetails, Position, Velocity, Stats)> = vec![]; + for (map, spawns) in all_spawns { + for monster in &spawns.monsters { + let mut current_monster = all_monster_definitions + .get(&monster.monster_type) + .unwrap_or_else(|| { + error!( + "Monster {} missing from all_monster_definitions.", + &monster.monster_type, + ); + process::exit(1); + }) + .clone(); + for spawn_position in &monster.spawn_positions { + let position = Position { + x: spawn_position.x, + y: spawn_position.y, + map: map.clone(), + }; + current_monster.monster_details.id = Uuid::new_v4(); + current_monster.monster_details.spawn_position = position.clone(); + current_monster.position = position; + info!( + "Spawned monster {} at position {:?}", + current_monster.monster_details.monster_type, current_monster.position + ); + monsters_vec.push(( + current_monster.monster_details.clone(), + current_monster.display_details, + current_monster.position, + current_monster.velocity, + current_monster.stats, + )); + } + } + } + world.extend(monsters_vec); +} + +pub(crate) fn update_velocities(world: &mut World) { + debug!("Updating monster velocities."); + let players_positions = players::get_current_player_positions(world); + + let mut query = <(&Position, &mut Velocity, &mut MonsterDetails)>::query(); + for (position, velocity, monster) in query.iter_mut(world) { + let mut moving_towards_existing_target = false; + + if let Some(target) = monster.current_target.clone() { + if let Some(current_target_position) = players_positions.get(&target) { + if is_specific_player_nearby(¤t_target_position, &position) { + *velocity = move_towards_target(position, current_target_position); + moving_towards_existing_target = true; + } + } + } + + if !moving_towards_existing_target { + let nearby_player = is_any_player_nearby(&players_positions, &position); + match nearby_player { + Some((player_name, player_position)) => { + monster.is_active = true; + monster.current_target = Some(player_name.clone()); + *velocity = move_towards_target(position, player_position); + } + None => { + debug!("Monster returning to spawn location"); + monster.is_active = false; + monster.current_target = None; + *velocity = move_towards_target(position, &monster.spawn_position); + } + } + } + } +} + +fn move_towards_target(monster_position: &Position, target_position: &Position) -> Velocity { + let diff_x = monster_position.x - target_position.x; + let diff_y = monster_position.y - target_position.y; + let mut new_pos_x = monster_position.x; + let mut new_pos_y = monster_position.y; + + match diff_x.abs().cmp(&diff_y.abs()) { + Ordering::Greater => new_pos_x = move_towards(diff_x, monster_position.x), + Ordering::Less => new_pos_y = move_towards(diff_y, monster_position.y), + Ordering::Equal => { + let mut rng = rand::thread_rng(); + if rng.gen::() { + new_pos_x = move_towards(diff_x, monster_position.x) + } else { + new_pos_y = move_towards(diff_y, monster_position.y) + } + } + } + Velocity { + x: new_pos_x - monster_position.x, + y: new_pos_y - monster_position.y, + } +} + +fn move_towards(diff: i32, position: i32) -> i32 { + if diff.abs() as u32 > 1 { + return match diff.is_positive() { + true => position - 1, + false => position + 1, + }; + } + position +} + +fn is_any_player_nearby<'a>( + player_positions: &'a HashMap, + monster_position: &Position, +) -> Option<(&'a String, &'a Position)> { + let monster_x_range = (monster_position.x - MONSTER_DISTANCE_ACTIVATION) + ..(monster_position.x + MONSTER_DISTANCE_ACTIVATION); + let monster_y_range = (monster_position.y - MONSTER_DISTANCE_ACTIVATION) + ..(monster_position.y + MONSTER_DISTANCE_ACTIVATION); + for (player_name, position) in player_positions { + if monster_x_range.contains(&position.x) + && monster_y_range.contains(&position.y) + && monster_position.map == position.map + { + debug!("There is a player near a monster"); + return Some((player_name, position)); + } + } + None +} + +fn is_specific_player_nearby( + current_target_position: &Position, + monster_position: &Position, +) -> bool { + let monster_x_range = (monster_position.x - MONSTER_DISTANCE_ACTIVATION) + ..(monster_position.x + MONSTER_DISTANCE_ACTIVATION); + let monster_y_range = (monster_position.y - MONSTER_DISTANCE_ACTIVATION) + ..(monster_position.y + MONSTER_DISTANCE_ACTIVATION); + + if monster_x_range.contains(¤t_target_position.x) + && monster_y_range.contains(¤t_target_position.y) + && monster_position.map == current_target_position.map + { + return true; + } + false +} diff --git a/rustyhack_server/src/game/player_updates.rs b/rustyhack_server/src/game/player_updates.rs index 516c52b..29de172 100644 --- a/rustyhack_server/src/game/player_updates.rs +++ b/rustyhack_server/src/game/player_updates.rs @@ -4,7 +4,9 @@ use crossbeam_channel::{Receiver, Sender}; use laminar::Packet; use legion::{IntoQuery, World}; use rustyhack_lib::ecs::components; -use rustyhack_lib::ecs::components::{DisplayDetails, PlayerDetails, Position, Velocity}; +use rustyhack_lib::ecs::components::{ + DisplayDetails, MonsterDetails, PlayerDetails, Position, Stats, Velocity, +}; use rustyhack_lib::ecs::player::Player; use rustyhack_lib::message_handler::player_message::{EntityUpdates, PlayerMessage, PlayerReply}; use std::collections::HashMap; @@ -48,11 +50,14 @@ pub(crate) fn process_player_messages( } fn set_player_disconnected(world: &mut World, address: String) { - let mut query = <&mut PlayerDetails>::query(); - for player_details in query.iter_mut(world) { + let mut query = <(&mut PlayerDetails, &mut DisplayDetails)>::query(); + for (player_details, display_details) in query.iter_mut(world) { if player_details.client_addr == address { + display_details.visible = false; + display_details.collidable = false; player_details.currently_online = false; player_details.client_addr = "".to_string(); + info!( "Player {} at {} now marked as disconnected.", &player_details.player_name, &address @@ -63,12 +68,14 @@ fn set_player_disconnected(world: &mut World, address: String) { } fn join_player(world: &mut World, name: String, client_addr: String, sender: &Sender) { - let mut query = <(&mut PlayerDetails, &DisplayDetails, &Position)>::query(); + let mut query = <(&mut PlayerDetails, &mut DisplayDetails, &Position, &Stats)>::query(); let mut should_create_new_player = true; - for (player_details, display_details, position) in query.iter_mut(world) { + for (player_details, display_details, position, stats) in query.iter_mut(world) { if player_details.player_name == name && !player_details.currently_online { player_details.currently_online = true; player_details.client_addr = client_addr.clone(); + display_details.collidable = true; + display_details.visible = true; info!( "Existing player \"{}\" logged in from: {}", name, &client_addr @@ -77,6 +84,7 @@ fn join_player(world: &mut World, name: String, client_addr: String, sender: &Se player_details: player_details.clone(), display_details: *display_details, position: position.clone(), + stats: *stats, }; send_player_joined_response(player, sender); should_create_new_player = false; @@ -109,6 +117,9 @@ fn create_player(world: &mut World, name: String, client_addr: String, sender: & player_name: name.clone(), client_addr, currently_online: true, + level: 1, + exp: 0, + gold: 0, }, ..Default::default() }; @@ -117,6 +128,7 @@ fn create_player(world: &mut World, name: String, client_addr: String, sender: & player.player_details.clone(), player.display_details, player.position.clone(), + player.stats, components::Velocity { x: 0, y: 0 }, )); info!("New player \"{}\" created: {:?}", name, &player_entity); @@ -178,21 +190,28 @@ pub(crate) fn send_player_updates( player_velocity_updates } -pub(crate) fn send_other_entities_updates(world: &mut World, sender: &Sender) { +pub(crate) fn send_other_entities_updates(world: &World, sender: &Sender) { let mut position_updates: HashMap = HashMap::new(); let mut display_details: HashMap = HashMap::new(); - let mut query = <(&PlayerDetails, &mut Position, &DisplayDetails)>::query(); + let mut query = <(&PlayerDetails, &Position, &DisplayDetails)>::query(); debug!("Getting all players positions"); - for (player_details, position, display) in query.iter_mut(world) { + for (player_details, position, display) in query.iter(world) { if player_details.currently_online { position_updates.insert(player_details.player_name.clone(), position.clone()); display_details.insert(player_details.player_name.clone(), *display); } } - let mut query2 = <&PlayerDetails>::query(); + let mut query = <(&MonsterDetails, &Position, &DisplayDetails)>::query(); + debug!("Getting all monster positions"); + for (monster_details, position, display) in query.iter(world) { + position_updates.insert(monster_details.id.to_string(), position.clone()); + display_details.insert(monster_details.id.to_string(), *display); + } + + let mut query = <&PlayerDetails>::query(); debug!("Sending entity updates to all players."); - for player_details in query2.iter_mut(world) { + for player_details in query.iter(world) { if player_details.currently_online { debug!("Sending entity updates to: {}", &player_details.client_addr); let response = serialize(&PlayerReply::UpdateOtherEntities(EntityUpdates { diff --git a/rustyhack_server/src/game/players.rs b/rustyhack_server/src/game/players.rs new file mode 100644 index 0000000..faa7ad9 --- /dev/null +++ b/rustyhack_server/src/game/players.rs @@ -0,0 +1,15 @@ +use legion::{IntoQuery, World}; +use rustyhack_lib::ecs::components::{PlayerDetails, Position}; +use std::collections::HashMap; + +pub(crate) fn get_current_player_positions(world: &mut World) -> HashMap { + let mut players_positions: HashMap = HashMap::new(); + let mut query = <(&Position, &PlayerDetails)>::query(); + for (position, player) in query.iter(world) { + //only add online players + if player.currently_online { + players_positions.insert(player.player_name.clone(), position.clone()); + } + } + players_positions +} diff --git a/rustyhack_server/src/game/spawns.rs b/rustyhack_server/src/game/spawns.rs new file mode 100644 index 0000000..09b8912 --- /dev/null +++ b/rustyhack_server/src/game/spawns.rs @@ -0,0 +1,75 @@ +use crate::consts; +use rustyhack_lib::file_utils; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; +use std::process; + +pub type AllSpawns = HashMap; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Spawns { + pub monsters: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MonsterSpawns { + pub monster_type: String, + pub spawn_positions: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct PositionWithoutMap { + pub x: i32, + pub y: i32, +} + +pub(crate) fn initialise_all_spawn_definitions() -> AllSpawns { + info!("About to initialise all spawn definitions"); + let mut all_spawns: HashMap = HashMap::new(); + let paths = file_utils::get_all_files_in_location(spawns_directory_location()); + for path in paths { + let unwrapped_path = path.unwrap(); + let name = String::from( + unwrapped_path + .file_name() + .to_str() + .unwrap() + .split('.') + .next() + .unwrap(), + ); + let spawns: Spawns = get_spawns_definition_from_path(&unwrapped_path.path()); + info!("Initialised spawn definitions for map: {:?}", &name); + all_spawns.insert(name, spawns); + } + all_spawns +} + +fn spawns_directory_location() -> PathBuf { + let mut file_location = file_utils::current_exe_location(); + file_location.pop(); + file_location.push(consts::ASSETS_DIRECTORY); + file_location.push(consts::SPAWNS_DIRECTORY); + file_location +} + +fn get_spawns_definition_from_path(path: &PathBuf) -> Spawns { + let file = File::open(path).unwrap_or_else(|err| { + error!( + "Problem getting spawns definition from file: {:?}, error: {}", + path, err + ); + process::exit(1); + }); + let buf_reader = BufReader::new(file); + serde_json::from_reader(buf_reader).unwrap_or_else(|err| { + error!( + "Problem deserialising spawns definition from file: {:?}, error: {}", + path, err + ); + process::exit(1); + }) +} diff --git a/rustyhack_server/src/game/systems.rs b/rustyhack_server/src/game/systems.rs index 4242a2f..139c859 100644 --- a/rustyhack_server/src/game/systems.rs +++ b/rustyhack_server/src/game/systems.rs @@ -1,20 +1,91 @@ +use crate::game::map_state; +use crate::game::map_state::AllMapStates; use legion::world::SubWorld; use legion::*; use rustyhack_lib::background_map::tiles::{Collidable, Tile}; use rustyhack_lib::background_map::AllMaps; use rustyhack_lib::consts::DEFAULT_MAP; -use rustyhack_lib::ecs::components::{DisplayDetails, PlayerDetails, Position, Velocity}; +use rustyhack_lib::ecs::components::{ + DisplayDetails, EntityType, MonsterDetails, PlayerDetails, Position, Stats, Velocity, +}; +use rustyhack_lib::ecs::monster::Monster; +use rustyhack_lib::ecs::player::Player; use std::collections::HashMap; -pub(crate) fn build_schedule() -> Schedule { +pub(crate) fn build_map_state_update_schedule() -> Schedule { + let schedule = Schedule::builder() + .add_system(reset_map_state_system()) + .add_system(add_entities_to_map_state_system()) + .build(); + info!("Built map state update system schedule."); + schedule +} + +pub(crate) fn build_player_update_schedule() -> Schedule { let schedule = Schedule::builder() .add_system(update_player_input_system()) .add_system(update_entities_position_system()) .build(); - info!("Built system schedule."); + info!("Built player update system schedule."); schedule } +pub(crate) fn build_monster_update_schedule() -> Schedule { + let schedule = Schedule::builder() + .add_system(update_entities_position_system()) + .build(); + info!("Built monster update system schedule."); + schedule +} + +#[system] +fn reset_map_state(#[resource] all_map_states: &mut AllMapStates) { + debug!("Clearing map state."); + map_state::clear_all_entities(all_map_states); +} + +#[system(for_each)] +fn add_entities_to_map_state( + position: &Position, + velocity: &Velocity, + display_detals: &DisplayDetails, + monster_details_option: Option<&MonsterDetails>, + player_details_option: Option<&PlayerDetails>, + stats_option: Option<&Stats>, + #[resource] all_map_states: &mut AllMapStates, +) { + debug!("Adding current entity positions to map state."); + if let Some(monster_details) = monster_details_option { + let monster = Monster { + monster_details: monster_details.clone(), + display_details: *display_detals, + position: position.clone(), + velocity: *velocity, + stats: *stats_option.unwrap(), + }; + map_state::insert_entity_at( + all_map_states.get_mut(&position.map).unwrap(), + EntityType::Monster(monster), + position.x as usize, + position.y as usize, + ); + } + if let Some(player_details) = player_details_option { + let player = Player { + player_details: player_details.clone(), + display_details: *display_detals, + position: position.clone(), + stats: *stats_option.unwrap(), + }; + map_state::insert_entity_at( + all_map_states.get_mut(&position.map).unwrap(), + EntityType::Player(player), + position.x as usize, + position.y as usize, + ); + } +} + #[system(par_for_each)] fn update_player_input( player_details: &PlayerDetails, @@ -31,13 +102,14 @@ fn update_player_input( } #[system] -#[write_component(Velocity)] -#[write_component(Position)] #[read_component(DisplayDetails)] -fn update_entities_position(world: &mut SubWorld, #[resource] all_maps: &AllMaps) { - let mut query = <(&mut Velocity, &mut Position)>::query(); - let world2 = &world.clone(); - for (velocity, position) in query.iter_mut(world) { +fn update_entities_position( + world: &mut SubWorld, + velocity_query: &mut Query<(&mut Velocity, &mut Position)>, + #[resource] all_maps: &AllMaps, + #[resource] all_map_states: &mut AllMapStates, +) { + for (velocity, position) in velocity_query.iter_mut(world) { debug!("Updating world entities positions after velocity updates."); let current_map = all_maps.get(&position.map).unwrap_or_else(|| { error!( @@ -50,11 +122,10 @@ fn update_entities_position(world: &mut SubWorld, #[resource] all_maps: &AllMaps if !entity_is_colliding_with_tile(current_map.get_tile_at( (position.x + velocity.x) as usize, (position.y + velocity.y) as usize, - )) && !entity_is_colliding_with_entity( - position.x + velocity.x, - position.y + velocity.y, - world2, - &position.map, + )) && !map_state::is_colliding( + (position.x + velocity.x) as usize, + (position.y + velocity.y) as usize, + all_map_states.get_mut(&position.map).unwrap(), ) { position.x += velocity.x; position.y += velocity.y; @@ -64,26 +135,6 @@ fn update_entities_position(world: &mut SubWorld, #[resource] all_maps: &AllMaps } } -fn entity_is_colliding_with_entity( - player_x: i32, - player_y: i32, - world: &SubWorld, - current_map: &str, -) -> bool { - let mut result = false; - let mut query = <(&Position, &DisplayDetails)>::query(); - for (position, display_details) in query.iter(world) { - if position.map == current_map - && display_details.collidable - && position.x == player_x - && position.y == player_y - { - result = true; - } - } - result -} - fn entity_is_colliding_with_tile(tile: Tile) -> bool { match tile { Tile::Door(door) => door.collidable == Collidable::True, diff --git a/rustyhack_server/src/setup.rs b/rustyhack_server/src/setup.rs index 5dc5cc1..6f61062 100644 --- a/rustyhack_server/src/setup.rs +++ b/rustyhack_server/src/setup.rs @@ -1,8 +1,9 @@ use crate::consts; +use rustyhack_lib::file_utils; use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode, WriteLogger}; use std::fs::File; use std::net::SocketAddr; -use std::{env, io, process}; +use std::{io, process}; pub(crate) fn initialise_log(args: Vec) { let mut log_level = LevelFilter::Info; @@ -10,10 +11,7 @@ pub(crate) fn initialise_log(args: Vec) { println!("Debug logging enabled."); log_level = LevelFilter::Debug; } - let mut file_location = env::current_exe().unwrap_or_else(|err| { - eprintln!("Problem getting current executable location: {}", err); - process::exit(1); - }); + let mut file_location = file_utils::current_exe_location(); file_location.pop(); file_location.push(consts::LOG_NAME); CombinedLogger::init(vec![