From cf9392a33bb99ae581f818d3ddb8be1231521a02 Mon Sep 17 00:00:00 2001 From: Katharina Fey Date: Sat, 6 Feb 2021 19:40:53 +0100 Subject: rstnode: restructure project into workspace and sub-crates --- games/rstnode/Cargo.lock | 115 +++++++----- games/rstnode/Cargo.toml | 34 +--- games/rstnode/assets/logo.svg | 5 +- games/rstnode/rst-client/Cargo.toml | 11 ++ games/rstnode/rst-client/src/main.rs | 3 + games/rstnode/rst-core/Cargo.toml | 29 ++++ games/rstnode/rst-core/src/_if.rs | 58 +++++++ games/rstnode/rst-core/src/_loop.rs | 41 +++++ games/rstnode/rst-core/src/_match.rs | 72 ++++++++ games/rstnode/rst-core/src/bin/game.rs | 3 + games/rstnode/rst-core/src/bin/server.rs | 3 + games/rstnode/rst-core/src/config/io.rs | 45 +++++ games/rstnode/rst-core/src/config/mod.rs | 50 ++++++ games/rstnode/rst-core/src/data.rs | 279 ++++++++++++++++++++++++++++++ games/rstnode/rst-core/src/gens.rs | 92 ++++++++++ games/rstnode/rst-core/src/io.rs | 22 +++ games/rstnode/rst-core/src/lib.rs | 39 +++++ games/rstnode/rst-core/src/lobby.rs | 191 ++++++++++++++++++++ games/rstnode/rst-core/src/map.rs | 80 +++++++++ games/rstnode/rst-core/src/mapstore.rs | 17 ++ games/rstnode/rst-core/src/runner.rs | 1 + games/rstnode/rst-core/src/server.rs | 155 +++++++++++++++++ games/rstnode/rst-core/src/stats.rs | 183 ++++++++++++++++++++ games/rstnode/rst-core/src/users.rs | 69 ++++++++ games/rstnode/rst-core/src/wire/action.rs | 33 ++++ games/rstnode/rst-core/src/wire/mod.rs | 68 ++++++++ games/rstnode/rst-core/src/wire/req.rs | 38 ++++ games/rstnode/rst-core/src/wire/resp.rs | 94 ++++++++++ games/rstnode/rst-core/src/wire/update.rs | 72 ++++++++ games/rstnode/rst-launcher/Cargo.toml | 9 + games/rstnode/rst-launcher/src/main.rs | 3 + games/rstnode/rst-server/Cargo.toml | 10 ++ games/rstnode/rst-server/src/main.rs | 3 + games/rstnode/shell.nix | 4 +- games/rstnode/src/_if.rs | 58 ------- games/rstnode/src/_loop.rs | 38 ---- games/rstnode/src/_match.rs | 67 ------- games/rstnode/src/bin/game.rs | 5 - games/rstnode/src/bin/server.rs | 6 - games/rstnode/src/config.rs | 41 ----- games/rstnode/src/data.rs | 279 ------------------------------ games/rstnode/src/gens.rs | 92 ---------- games/rstnode/src/io.rs | 22 --- games/rstnode/src/lib.rs | 24 --- games/rstnode/src/lobby.rs | 191 -------------------- games/rstnode/src/map.rs | 80 --------- games/rstnode/src/mapstore.rs | 17 -- games/rstnode/src/runner.rs | 1 - games/rstnode/src/server.rs | 155 ----------------- games/rstnode/src/stats.rs | 183 -------------------- games/rstnode/src/users.rs | 69 -------- games/rstnode/src/wire/action.rs | 33 ---- games/rstnode/src/wire/mod.rs | 70 -------- games/rstnode/src/wire/req.rs | 39 ----- games/rstnode/src/wire/resp.rs | 94 ---------- games/rstnode/src/wire/update.rs | 72 -------- 56 files changed, 1858 insertions(+), 1709 deletions(-) create mode 100644 games/rstnode/rst-client/Cargo.toml create mode 100644 games/rstnode/rst-client/src/main.rs create mode 100644 games/rstnode/rst-core/Cargo.toml create mode 100644 games/rstnode/rst-core/src/_if.rs create mode 100644 games/rstnode/rst-core/src/_loop.rs create mode 100644 games/rstnode/rst-core/src/_match.rs create mode 100644 games/rstnode/rst-core/src/bin/game.rs create mode 100644 games/rstnode/rst-core/src/bin/server.rs create mode 100644 games/rstnode/rst-core/src/config/io.rs create mode 100644 games/rstnode/rst-core/src/config/mod.rs create mode 100644 games/rstnode/rst-core/src/data.rs create mode 100644 games/rstnode/rst-core/src/gens.rs create mode 100644 games/rstnode/rst-core/src/io.rs create mode 100644 games/rstnode/rst-core/src/lib.rs create mode 100644 games/rstnode/rst-core/src/lobby.rs create mode 100644 games/rstnode/rst-core/src/map.rs create mode 100644 games/rstnode/rst-core/src/mapstore.rs create mode 100644 games/rstnode/rst-core/src/runner.rs create mode 100644 games/rstnode/rst-core/src/server.rs create mode 100644 games/rstnode/rst-core/src/stats.rs create mode 100644 games/rstnode/rst-core/src/users.rs create mode 100644 games/rstnode/rst-core/src/wire/action.rs create mode 100644 games/rstnode/rst-core/src/wire/mod.rs create mode 100644 games/rstnode/rst-core/src/wire/req.rs create mode 100644 games/rstnode/rst-core/src/wire/resp.rs create mode 100644 games/rstnode/rst-core/src/wire/update.rs create mode 100644 games/rstnode/rst-launcher/Cargo.toml create mode 100644 games/rstnode/rst-launcher/src/main.rs create mode 100644 games/rstnode/rst-server/Cargo.toml create mode 100644 games/rstnode/rst-server/src/main.rs delete mode 100644 games/rstnode/src/_if.rs delete mode 100644 games/rstnode/src/_loop.rs delete mode 100644 games/rstnode/src/_match.rs delete mode 100644 games/rstnode/src/bin/game.rs delete mode 100644 games/rstnode/src/bin/server.rs delete mode 100644 games/rstnode/src/config.rs delete mode 100644 games/rstnode/src/data.rs delete mode 100644 games/rstnode/src/gens.rs delete mode 100644 games/rstnode/src/io.rs delete mode 100644 games/rstnode/src/lib.rs delete mode 100644 games/rstnode/src/lobby.rs delete mode 100644 games/rstnode/src/map.rs delete mode 100644 games/rstnode/src/mapstore.rs delete mode 100644 games/rstnode/src/runner.rs delete mode 100644 games/rstnode/src/server.rs delete mode 100644 games/rstnode/src/stats.rs delete mode 100644 games/rstnode/src/users.rs delete mode 100644 games/rstnode/src/wire/action.rs delete mode 100644 games/rstnode/src/wire/mod.rs delete mode 100644 games/rstnode/src/wire/req.rs delete mode 100644 games/rstnode/src/wire/resp.rs delete mode 100644 games/rstnode/src/wire/update.rs diff --git a/games/rstnode/Cargo.lock b/games/rstnode/Cargo.lock index d6074e27d20a..d41147a5e623 100644 --- a/games/rstnode/Cargo.lock +++ b/games/rstnode/Cargo.lock @@ -27,7 +27,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" dependencies = [ - "approx 0.3.2", + "approx", "num-complex", "num-traits 0.2.14", ] @@ -71,15 +71,6 @@ dependencies = [ "num-traits 0.2.14", ] -[[package]] -name = "approx" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" -dependencies = [ - "num-traits 0.2.14", -] - [[package]] name = "arrayref" version = "0.3.6" @@ -289,9 +280,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59" +checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" [[package]] name = "byte-tools" @@ -323,9 +314,9 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.9+1.0.8" +version = "0.1.10+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3b39a260062fca31f7b0b12f207e8f2590a67d32ec7d59c20484b07ea7285e" +checksum = "17fa3d1ac1ca21c5c4e36a97f3c3eb25084576f6fc47bf0139c1123434216c6c" dependencies = [ "cc", "libc", @@ -381,17 +372,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cgmath" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" -dependencies = [ - "approx 0.4.0", - "mint", - "num-traits 0.2.14", -] - [[package]] name = "chrono" version = "0.4.19" @@ -648,9 +628,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10bcb9d7dcbf7002aaffbb53eac22906b64cdcc127971dcc387d8eb7c95d5560" +checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" dependencies = [ "quote 1.0.8", "syn 1.0.60", @@ -786,6 +766,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + [[package]] name = "error-chain" version = "0.12.4" @@ -1069,7 +1055,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a76347431050a0b4e1a925124cdcde614b628260093bb52560d9c6b48435336" dependencies = [ - "approx 0.3.2", + "approx", "bitflags", "directories", "gfx", @@ -1274,7 +1260,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b70adc570f1dc71b6b32e241cbcc2b42175f5aea71951fbf41e68b04aec24c7" dependencies = [ - "approx 0.3.2", + "approx", "rusttype 0.8.3", "xi-unicode", ] @@ -1380,9 +1366,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "jpeg-decoder" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6187dc218616c3a222bae7b78938f5af5e7c27d9cbef4c30ebca709cd680bf8" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "kernel32-sys" @@ -1434,9 +1420,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" [[package]] name = "libloading" @@ -1715,7 +1701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaa9fddbc34c8c35dd2108515587b8ce0cab396f17977b8c738568e4edb521a2" dependencies = [ "alga", - "approx 0.3.2", + "approx", "generic-array 0.12.3", "matrixmultiply", "mint", @@ -2511,22 +2497,40 @@ dependencies = [ ] [[package]] -name = "rstnode" +name = "rst-core" version = "0.0.0" dependencies = [ "async-std", "async-trait", - "cgmath", + "bincode", "chrono", "const_env", - "ggez", "netmod-mem", "quadtree_rs", "rand 0.7.3", "ratman", "ratman-identity 0.4.0", "serde", - "serde_json", + "serde_yaml", +] + +[[package]] +name = "rst-launcher" +version = "0.1.0" + +[[package]] +name = "rst-node-client" +version = "0.0.0" +dependencies = [ + "ggez", + "rst-core", +] + +[[package]] +name = "rst-node-server" +version = "0.0.0" +dependencies = [ + "rst-core", ] [[package]] @@ -2577,7 +2581,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0" dependencies = [ - "approx 0.3.2", + "approx", "crossbeam-deque", "crossbeam-utils 0.7.2", "linked-hash-map", @@ -2657,15 +2661,27 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" +checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd2af560da3c1fdc02cb80965289254fc35dff869810061e2d8290ee48848ae" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + [[package]] name = "sha1" version = "0.6.0" @@ -2957,9 +2973,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" +checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3" dependencies = [ "cfg-if 1.0.0", "pin-project-lite 0.2.4", @@ -2969,9 +2985,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" +checksum = "43f080ea7e4107844ef4766459426fa2d5c1ada2e47edba05dc7fa99d9629f47" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", @@ -3303,6 +3319,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zip" version = "0.5.9" diff --git a/games/rstnode/Cargo.toml b/games/rstnode/Cargo.toml index a8bfe703e236..4aa2532acf2d 100644 --- a/games/rstnode/Cargo.toml +++ b/games/rstnode/Cargo.toml @@ -1,27 +1,11 @@ -[package] -name = "rstnode" -description = "A game about capturing computers on a network" -version = "0.0.0" -edition = "2018" -license = "AGPL-3.0-or-later" -authors = ["Bread Machine", "Katharina Fey "] +[workspace] +members = [ + "rst-core", -[dependencies] -serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = "1.0" -async-std = { version = "1.0", features = ["unstable"] } -async-trait = "0.1" + "rst-launcher", + "rst-client", + "rst-server", +] -ratman = "0.1" -netmod-mem = "0.1.0" -identity = { version = "0.4", features = ["random"], package = "ratman-identity"} - -ggez = "0.5" -cgmath = { version = "*", features = ["mint"] } -chrono = { version = "0.4", features = ["serde"] } -rand = "0.7" -quadtree_rs = "0.1" -const_env = "0.1" - -[profile.release.overrides."*"] -opt-level = 3 \ No newline at end of file +# [profile.release.overrides."*"] +# opt-level = 3 \ No newline at end of file diff --git a/games/rstnode/assets/logo.svg b/games/rstnode/assets/logo.svg index 2bb655b75b63..0049d99c53fa 100644 --- a/games/rstnode/assets/logo.svg +++ b/games/rstnode/assets/logo.svg @@ -13,7 +13,10 @@ version="1.1" id="svg8" inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" - sodipodi:docname="logo.svg"> + sodipodi:docname="logo.svg" + inkscape:export-filename="/home/Projects/kookienomicon/games/rstnode/assets/logo.png" + inkscape:export-xdpi="200.07384" + inkscape:export-ydpi="200.07384"> "] + +[dependencies] +rst-core = { path = "../rst-core" } +ggez = "0.5" \ No newline at end of file diff --git a/games/rstnode/rst-client/src/main.rs b/games/rstnode/rst-client/src/main.rs new file mode 100644 index 000000000000..e7a11a969c03 --- /dev/null +++ b/games/rstnode/rst-client/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/games/rstnode/rst-core/Cargo.toml b/games/rstnode/rst-core/Cargo.toml new file mode 100644 index 000000000000..da0d3fa0ad55 --- /dev/null +++ b/games/rstnode/rst-core/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rst-core" +description = "Shared logic and type state library for rstnode" +version = "0.0.0" +edition = "2018" +license = "AGPL-3.0-or-later" +authors = ["Bread Machine", "Katharina Fey "] + +[dependencies] + +# Serialisation +bincode = "1.0" +serde = { version = "1.0", features = ["derive", "rc"] } +serde_yaml = "0.8.15" + +# Async utils +async-std = { version = "1.0", features = ["unstable"] } +async-trait = "0.1" + +# Ratman networking utils +identity = { version = "0.4", features = ["random"], package = "ratman-identity"} +netmod-mem = "0.1.0" +ratman = "0.1" + +# Other dependencies +chrono = { version = "0.4", features = ["serde"] } +const_env = "0.1" +quadtree_rs = "0.1" +rand = "0.7" diff --git a/games/rstnode/rst-core/src/_if.rs b/games/rstnode/rst-core/src/_if.rs new file mode 100644 index 000000000000..1a842840708c --- /dev/null +++ b/games/rstnode/rst-core/src/_if.rs @@ -0,0 +1,58 @@ +//! A common trait interface between the server and the client + +use crate::wire::{ + Action, AuthErr, Lobby, LobbyErr, LobbyId, LobbyUpdate, MatchErr, MatchId, RegErr, UpdateState, + User, UserId, +}; +use async_std::sync::Arc; +use async_trait::async_trait; +use chrono::{DateTime, Utc}; + +/// Main game interface implemented by the server and client +/// +/// The client implementation simply translates requests to network +/// requests that are sent to the server. The server implementation +/// consists of two parts: the network layer listening loop, and the +/// game server state which then implements the actual game logic. +#[async_trait] +pub trait GameIf { + /// Register a new user on a game server + async fn register(self: Arc, name: String, pw: String) -> Result; + + /// Login for an existing user + async fn login(self: Arc, name: String, pw: String) -> Result; + + /// End a user session (go offline) + async fn logout(self: Arc, user: User) -> Result<(), AuthErr>; + + /// Register as an anonymous player + async fn anonymous(self: Arc, name: String) -> Result; + + /// Join a match-making lobby + async fn join(self: Arc, user: User, lobby: LobbyId) -> Result; + + /// Leave a match-making lobby + async fn leave(self: Arc, user: User, lobby: LobbyId) -> Result<(), LobbyErr>; + + /// Set the player's ready state + async fn ready( + self: Arc, + user: User, + lobby: LobbyId, + ready: bool, + ) -> Result; + + /// Send a start request (as lobby admin) + async fn start_req( + self: Arc, + user: UserId, + lobby: LobbyId, + ) -> Result, LobbyErr>; + + /// Perform a game action as a user + async fn perform_action(self: Arc, user: User, mtch: MatchId, act: Action) + -> UpdateState; + + /// Leave a match + async fn leave_match(self: Arc, user: User, mtch: MatchId) -> Result<(), MatchErr>; +} diff --git a/games/rstnode/rst-core/src/_loop.rs b/games/rstnode/rst-core/src/_loop.rs new file mode 100644 index 000000000000..ac4619622ed3 --- /dev/null +++ b/games/rstnode/rst-core/src/_loop.rs @@ -0,0 +1,41 @@ +//! A timed loop implementation +//! +//! This is a utility to be used in the server simulation to make sure +//! that a simulation step takes a certain amount of time to execute. + +use async_std::{future::Future, task}; +use chrono::{DateTime, Utc}; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; + +/// Number of ticks per second +#[from_env("RSTNODE_TICKS")] +const TICKS: u64 = 100; +const TICK_TIME: Duration = Duration::from_millis(1000 / TICKS); + +/// Run a timed loop until you no longer want to +pub(crate) fn block_loop(run: Arc, f: F) +where + F: Future + Send + Copy + 'static, +{ + while run.load(Ordering::Relaxed) { + let t1 = Utc::now(); + task::block_on(f); + let t2 = Utc::now(); + let t3 = (t2 - t1).to_std().unwrap(); + task::block_on(async { task::sleep(TICK_TIME - t3) }); + } +} + +/// Run a detached timed loop until you no longer want to +pub(crate) fn spawn_loop(run: Arc, f: F) +where + F: Future + Send + Copy + 'static, +{ + task::spawn(async move { block_loop(run, f) }); +} diff --git a/games/rstnode/rst-core/src/_match.rs b/games/rstnode/rst-core/src/_match.rs new file mode 100644 index 000000000000..ce75af5af393 --- /dev/null +++ b/games/rstnode/rst-core/src/_match.rs @@ -0,0 +1,72 @@ +use crate::{ + data::Player, + lobby::MetaLobby, + map::Map, + wire::{Action, LobbyUser, MatchId, UserId}, +}; +use async_std::sync::{Arc, RwLock}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; + +/// Describes a match for the server +/// +/// This type implements the partial [GameIf](crate::GameIf) API to +/// allow client to queue commands from players. The server +/// implementation runs a simulation for each match that is running on +/// it. +pub struct Match { + /// The match id + pub id: MatchId, + /// The list of active players + pub players: Vec, + /// The active game map + pub map: Map, + /// Input inbox (handled in-order each game tick) + inbox: RwLock>, + /// The time the match was initialised + init_t: DateTime, + /// The synced time the match was started + start_t: Option>, +} + +impl From for Match { + fn from(ml: MetaLobby) -> Self { + Self { + id: ml.inner.id, + players: ml + .inner + .players + .into_iter() + .map(|lu| Player { + id: lu.id, + name: lu.name, + color: lu.color, + money: 0.into(), + }) + .collect(), + map: Map::new(), + inbox: Default::default(), + init_t: Utc::now(), + start_t: None, + } + } +} + +impl Match { + /// Set the start time of the match, which may be in the future + pub fn set_start(&mut self, t: DateTime) { + self.start_t = Some(t); + } + + /// Queue a new game action + pub async fn queue(&self, cmd: Action) { + self.inbox.write().await.push_back(cmd); + } + + pub async fn handle_inbox(&mut self) { + // for act in self.inbox.write().await.drain() { + + // } + } +} diff --git a/games/rstnode/rst-core/src/bin/game.rs b/games/rstnode/rst-core/src/bin/game.rs new file mode 100644 index 000000000000..f826c64689db --- /dev/null +++ b/games/rstnode/rst-core/src/bin/game.rs @@ -0,0 +1,3 @@ +//! The main game UI client + +fn main() {} diff --git a/games/rstnode/rst-core/src/bin/server.rs b/games/rstnode/rst-core/src/bin/server.rs new file mode 100644 index 000000000000..357ddf944dc4 --- /dev/null +++ b/games/rstnode/rst-core/src/bin/server.rs @@ -0,0 +1,3 @@ +//! The dedicated server binary + +fn main() {} diff --git a/games/rstnode/rst-core/src/config/io.rs b/games/rstnode/rst-core/src/config/io.rs new file mode 100644 index 000000000000..07aa7e7b3d3f --- /dev/null +++ b/games/rstnode/rst-core/src/config/io.rs @@ -0,0 +1,45 @@ +//! I/O utilities to load configurations from disk + +use super::MapCfg; +use serde_yaml::from_str; +use std::{ + fs::File, + io::{self, Read}, + path::Path, +}; + +/// Encode error state while loading a map configuration +pub enum Error { + Io(String), + Parsing(String), +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Self::Io(e.to_string()) + } +} + +impl From for Error { + fn from(e: serde_yaml::Error) -> Self { + Self::Parsing(e.to_string()) + } +} + +/// Load a map YAML configuration from disk +/// +/// This file format is structured according to the configuration +/// types in [config](crate::config). An example configuration is +/// provided below. +/// +/// ```yaml +/// nodes: +/// - +/// ``` +pub fn load_map<'p>(p: impl Into<&'p Path>) -> Result { + let mut f = File::open(p.into())?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + + Ok(from_str(s.as_str())?) +} diff --git a/games/rstnode/rst-core/src/config/mod.rs b/games/rstnode/rst-core/src/config/mod.rs new file mode 100644 index 000000000000..447c0fcbcc35 --- /dev/null +++ b/games/rstnode/rst-core/src/config/mod.rs @@ -0,0 +1,50 @@ +//! The file formats backing maps and other configs +//! +//! When creating maps to share with other clients, or to load into a +//! server, these types can be constructed with the YML description +//! format, outlined in [load_map](config::io::load_map) + +mod io; +pub use io::*; + +use crate::data::{LinkId, NodeId}; +use serde::{Deserialize, Serialize}; + +/// A config tree that describes a map +#[derive(Serialize, Deserialize)] +pub struct MapCfg { + /// The set of nodes + pub nodes: Vec, + /// Links connecting nodes + pub links: Vec, + /// Default spawn points (player count) + pub spawns: Vec, +} + +/// A single node on a map, with an x and y coordinate +#[derive(Serialize, Deserialize)] +pub struct NodeCfg { + /// Node ID + pub id: NodeId, + /// Render/world position + pub x: f64, + pub y: f64, +} + +/// A link between two nodes +#[derive(Serialize, Deserialize)] +pub struct LinkCfg { + /// The link ID + id: LinkId, + /// List of connectioned nodes + con: (NodeId, NodeId), +} + +/// Special configuration for a node to act as a player spawn point +#[derive(Serialize, Deserialize)] +pub struct SpawnCfg { + /// The node of the spawn point + pub n_id: NodeId, + /// At what number of players is this spawn available? + pub max_players: usize, +} diff --git a/games/rstnode/rst-core/src/data.rs b/games/rstnode/rst-core/src/data.rs new file mode 100644 index 000000000000..5ce5517c2b07 --- /dev/null +++ b/games/rstnode/rst-core/src/data.rs @@ -0,0 +1,279 @@ +//! Data structures for the game + +use crate::io::Io; +use async_std::sync::Arc; +use rand::seq::SliceRandom; +use rand::thread_rng; +use serde::{Deserialize, Serialize}; +use std::{ + collections::BTreeMap, + sync::atomic::{AtomicBool, AtomicU16, AtomicU32}, +}; + +pub type NodeId = usize; + +/// A node is a computer on the network graph +/// +/// It's owned by a player, and has some upgrade state, as well as +/// base stats. +#[derive(Serialize, Deserialize)] +pub struct Node { + /// Each node has a unique ID by which it's addressed + pub id: NodeId, + /// The current health + pub health: AtomicU32, + /// The max health + pub max_health: AtomicU32, + /// The owner of this node + pub owner: Owner, + /// Upgrade state + pub type_: Upgrade, + /// Number of links on the map + pub links: u8, + /// Active link states + pub link_states: Vec>, + /// Input buffer + #[serde(skip)] + pub buffer: Vec, +} + +pub type LinkId = usize; + +/// A one-to-one link between two nodes +#[derive(Serialize, Deserialize)] +pub struct Link { + /// This link ID + id: LinkId, + /// Node 1 + a: NodeId, + /// Node 2 + b: NodeId, + /// The step length + length: usize, + /// Packets present on this link + #[serde(skip)] + pp: Vec<(Packet, AtomicU32)>, + /// Actual Rx, Tx pair + #[serde(skip)] + io: Io, +} + +pub type PacketId = usize; + +/// A packet going across the network +pub struct Packet { + /// The packet ID + id: PacketId, + /// Declare this packet to be removed + dead: AtomicBool, + /// Each packet is owned by a player + owner: Arc, + /// What type of packet this is + data_: PacketType, +} + +pub type PlayerId = usize; + +/// A player who's having fun +#[derive(Serialize, Deserialize)] +pub struct Player { + /// A unique player ID (per match) + pub id: PlayerId, + /// The player name + pub name: String, + /// Player color + pub color: Color, + /// The player's money + pub money: AtomicU16, +} + +/// Optionally, players can create teams +#[derive(Serialize, Deserialize)] +pub struct Team { + /// Name of the team + name: String, + /// Unified color of the team + color: Color, + /// All team members by their ID + roster: Vec, +} + +/// An RGB color without alpha +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Color(u8, u8, u8); + +impl Color { + pub fn black() -> Self { + Self(50, 50, 50) + } + + pub fn red() -> Self { + Self(250, 50, 50) + } + + pub fn green() -> Self { + Self(100, 250, 100) + } + + pub fn blue() -> Self { + Self(100, 100, 250) + } + + pub fn teal() -> Self { + Self(150, 250, 250) + } + + pub fn purple() -> Self { + Self(150, 100, 250) + } + + pub fn orange() -> Self { + Self(250, 200, 100) + } + + pub fn yellow() -> Self { + Self(250, 250, 100) + } + + pub fn white() -> Self { + Self(225, 225, 225) + } +} + +pub trait ColorPalette { + /// Create a new color palette + fn palette() -> Self; + /// Get a palette without a certain colour + fn without(&mut self, b: &Color); + /// Mix a color back into the available palette + fn remix(&mut self, new: Color); +} + +impl ColorPalette for Vec { + fn palette() -> Self { + let mut rng = thread_rng(); + let mut pal = vec![ + Color::black(), + Color::red(), + Color::green(), + Color::blue(), + Color::teal(), + Color::purple(), + Color::orange(), + Color::yellow(), + Color::white(), + ]; + pal.shuffle(&mut rng); + pal + } + + /// Drop a colour from the palette + fn without(&mut self, b: &Color) { + if let Some(pos) = self.into_iter().rposition(|a| a == b) { + self.remove(pos); + } + } + + fn remix(&mut self, new: Color) { + let mut rng = thread_rng(); + self.push(new); + self.shuffle(&mut rng); + } +} + +/// Describes ownership state +#[derive(Serialize, Deserialize)] +pub enum Owner { + /// Nobody owns this + Neutral, + /// A player owns this + Player(Player), +} + +/// Encodes upgrade level without numbers +#[derive(Copy, Clone, Serialize, Deserialize)] +pub enum Level { + /// 1 + One, + /// 2 + Two, + /// 3 (wow) + Three, +} + +/// Describes upgrade state +#[derive(Copy, Clone, Serialize, Deserialize)] +pub enum Upgrade { + /// A basic node + Base, + /// Battle (attack/defence) nodes + Guard(Level), + /// These nodes make money + Compute(Level), + /// Good at packet switching + Relay(Level), +} + +/// Possible types of packets +pub enum PacketType { + /// A keepalive packet + /// + /// These are sent by all nodes if their neighbours are either + /// friendly or neutral, and used to keep the network alive. + /// Sending them costs nothing, and when they are received, they + /// yield a small amount of funds, and restoring health of a node. + Ping, + /// A non exploit capture + /// + /// This is a packet that can be sent out by any node (except a + /// switch) to claim a neutral node on the network. The path to + /// the node needs to consist only of friendlies, and if an enemy + /// node processes this type, nothing happens. + Capture, + /// A compute packet + /// + /// The first value is the target compute value, which each + /// compute node adds on to. The second value is the current. If + /// a compute packet passes through a compromised or enemy node, + /// it might subtract from the second value, before palling it on. + Compute { + max: u16, + curr: AtomicU16, + step: u16, + }, + /// A special wrapper packet generated by guards + /// + /// Sometimes, when a hostily attack packet encounters a guard, it + /// manages to capture the attack, and forwards it to a random + /// compute node. If the node manages to handle the packet, + /// without it getting dropped in the meantime), it yields a + /// specified reward, like a computation would. + Payload { inner: Box, reward: u16 }, + /// A reset attack packet + /// + /// When encountering a hostile node, it will make that node drop + /// all packets in it's buffers. + Reset, + /// Cross-node-scripting attack + /// + /// Decreases the strength of a node, also having a small chance + /// of spawning a new packet into a random output buffer. When + /// applied to a neutral node, it makes capturing nodes go faster. + CNS, + /// Node-in-the-middle attack + /// + /// Infect the routing behaviour of a node to route all traffic to + /// a specified enemy node instead + Nitm, + /// Virus infection attack + /// + /// Infects a node to capture it's earnings, both active and + /// passive, for a short time, without taking on it's costs. + Virus, + /// A total control exploit + /// + /// This is very hard to do, and a node will basically always + /// resist it, but if successful, transforms the node into a guard + /// node and yields control to the attackinng player. + TakeOver, +} diff --git a/games/rstnode/rst-core/src/gens.rs b/games/rstnode/rst-core/src/gens.rs new file mode 100644 index 000000000000..e6859f04ec66 --- /dev/null +++ b/games/rstnode/rst-core/src/gens.rs @@ -0,0 +1,92 @@ +//! Helpers to determine if a node can send a particular packet. + +/// These functions are unfortunately all a bit stringly typed atm +pub mod can_send { + use crate::data::{Level, PacketType, Upgrade}; + use Level::*; + + #[inline] + pub fn base() -> Vec<&'static str> { + vec!["ping", "capture"] + } + + /// Guard nodes are the most versatile + #[inline] + pub fn guard(lvl: Level) -> Vec<&'static str> { + match lvl { + // This is just kinda gross + One => { + let mut p = base(); + p.append(&mut vec!["payload", "cns", "reset"]); + p + } + Two => { + let mut p = guard(One); + p.append(&mut vec!["nitm", "virus"]); + p + } + Three => { + let mut p = guard(Two); + p.append(&mut vec!["takeover"]); + p + } + } + } + + /// Compute nodes can ping and compute but not capture + pub fn compute() -> Vec<&'static str> { + vec!["ping", "compute"] + } + + /// Relays only relay! + pub fn relay() -> Vec<&'static str> { + vec![] + } +} + +pub mod send { + use crate::{ + data::{ + Level::*, + PacketType as Pkt, + Upgrade::{self, *}, + }, + stats::strengths, + }; + + /// Turn the string type identifier into a packet + /// + /// This function makes heavy use of the stats module, which is + /// responsible for balancing all of these values. + pub fn build_packet(node: Upgrade, type_: &'static str, prev: Option) -> Pkt { + match (prev, node, type_) { + // All pings are the same + (None, _, "ping") => Pkt::Ping, + + // TODO: captures should improve + (None, Base, "capture") | (None, Guard(_), "capture") => Pkt::Capture, + + (None, Compute(lvl), "compute") => Pkt::Compute { + curr: Default::default(), + max: strengths::compute_max(lvl), + step: strengths::compute_step(lvl), + }, + + (Some(prev), Guard(lvl), "payload") => Pkt::Payload { + inner: Box::new(prev), + reward: strengths::payload_reward(lvl), + }, + + (None, Guard(_), "cns") => Pkt::CNS, + (None, Guard(_), "reset") => Pkt::Reset, + (None, Guard(_), "nitm") => Pkt::Nitm, + (None, Guard(_), "virus") => Pkt::Virus, + + // Only level 3 guards can send takeovers + (None, Guard(Three), "takeover") => Pkt::TakeOver, + + // Can't touch this + (_, _, _) => unreachable!(), + } + } +} diff --git a/games/rstnode/rst-core/src/io.rs b/games/rstnode/rst-core/src/io.rs new file mode 100644 index 000000000000..3032c2698190 --- /dev/null +++ b/games/rstnode/rst-core/src/io.rs @@ -0,0 +1,22 @@ +//! A module that adapts the ratman layer to RstNode + +use netmod_mem::MemMod; + +/// A pair of exits, connected +pub struct Io { + a: MemMod, + b: MemMod, +} + +impl Io { + fn new() -> Self { + Self::default() + } +} + +impl Default for Io { + fn default() -> Self { + let (a, b) = MemMod::make_pair(); + Self { a, b } + } +} diff --git a/games/rstnode/rst-core/src/lib.rs b/games/rstnode/rst-core/src/lib.rs new file mode 100644 index 000000000000..36e232f4639c --- /dev/null +++ b/games/rstnode/rst-core/src/lib.rs @@ -0,0 +1,39 @@ +//! # RST Node +//! +//! RST Node is a real-time strategy game about computers on a +//! network, fighting for dominance against a set of other network +//! operators. To operate a successful network you need to build +//! infrastructure, compute clusters, and defences on edge nodes. +//! +//! The game architecture is split between the game client and game +//! server. This library implements all required types and functions +//! to manage this state over a network connection. +//! +//! The main game interface is provided by [GameIf](crate::GameIf), +//! which is them implemented by [Server](crate::server::Server), and +//! [MatchClient](crate::client::MatchClient). + +#[macro_use] +extern crate const_env; + +pub(crate) mod _loop; + +mod _if; +pub use _if::GameIf; + +mod _match; +pub use _match::Match; + +pub mod config; +pub mod data; +pub mod gens; +pub mod io; +pub mod lobby; +pub mod map; +pub mod mapstore; +pub mod server; +pub mod stats; +pub mod users; +pub mod wire; + +pub use identity::Identity as Id; diff --git a/games/rstnode/rst-core/src/lobby.rs b/games/rstnode/rst-core/src/lobby.rs new file mode 100644 index 000000000000..496f98bd7b6f --- /dev/null +++ b/games/rstnode/rst-core/src/lobby.rs @@ -0,0 +1,191 @@ +//! The code that handles the lobby logic + +use crate::{ + data::{Color, ColorPalette}, + users::MetaUser, + wire::{Lobby, LobbyErr, LobbyId, LobbyUpdate, LobbyUser, User, UserId}, +}; +use async_std::sync::{Arc, RwLock}; +use std::{ + collections::BTreeMap, + sync::atomic::{AtomicUsize, Ordering}, +}; + +/// A list of all the lobbies on the server +pub struct LobbyList { + max: AtomicUsize, + lobbies: RwLock>, +} + +impl LobbyList { + pub fn new() -> Self { + Self { + max: 0.into(), + lobbies: Default::default(), + } + } + + /// Create a new lobby + pub async fn create(&self, map: String) -> LobbyId { + let id = self.max.fetch_add(1, Ordering::Relaxed); + self.lobbies + .write() + .await + .insert(id, MetaLobby::create(id, map)); + id + } + + /// Remove a lobby by ID + pub async fn destroy(&self, id: LobbyId) -> Result<(), LobbyErr> { + self.consume(id).await.map(|_| ()) + } + + /// Remove and return the lobby + pub async fn consume(&self, id: LobbyId) -> Result { + self.lobbies + .write() + .await + .remove(&id) + .map_or(Err(LobbyErr::NoSuchRoom), |l| Ok(l)) + } + + /// Get mutable access to a lobby + pub async fn get_mut(&self, id: LobbyId, cb: F) -> Result + where + F: Fn(&mut MetaLobby) -> T, + { + self.lobbies + .write() + .await + .get_mut(&id) + .map_or(Err(LobbyErr::OtherError), |ref mut l| Ok(cb(l))) + } +} + +/// Additional state held by the server +/// +/// The meta lobby will also sync updates to all connected users, when updates are made to the lobby +pub struct MetaLobby { + pub palette: Vec, + pub inner: Lobby, +} + +impl MetaLobby { + pub fn create(id: LobbyId, map: String) -> Self { + Self { + palette: Vec::palette(), + inner: Lobby { + id, + map, + players: vec![], + settings: vec![], + }, + } + } + + pub fn join(&mut self, user: &MetaUser) -> Lobby { + let color = if &user.name == "spacekookie" { + let color = Color::blue(); + self.palette.without(&color); + + if let Some(user) = self + .inner + .players + .iter_mut() + .find(|u| u.color == Color::blue()) + { + user.color = self.palette.remove(0); + } + + color + } else { + self.palette.remove(0) + }; + + self.inner.players.push(LobbyUser { + admin: false, + id: user.id, + name: user.name.clone(), + ready: false, + color, + }); + + self.inner.clone() + } + + pub fn leave(&mut self, user: &MetaUser) { + let (pos, user) = self + .inner + .players + .iter() + .enumerate() + .find_map(|(num, u)| { + if u.id == user.id { + Some((num, u)) + } else { + None + } + }) + .unwrap(); + self.palette.remix(user.color); + self.inner.players.remove(pos); + } + + /// Check if a user is even present in a lobby + /// + /// Perform this prerequisite check before making other user-specific changes to the lobby + pub fn in_lobby(&self, user: UserId) -> bool { + self.inner + .players + .iter() + .find(|u| u.id == user) + .map(|_| true) + .unwrap_or(false) + } + + /// Set the ready state for a user + pub fn ready(&mut self, user: User, ready: bool) -> LobbyUpdate { + if let Some(user) = self + .inner + .players + .iter_mut() + .find(|u| u.id == user.id) + .as_mut() + { + user.ready = ready; + } + + LobbyUpdate::Ready( + self.inner + .players + .iter() + .filter_map(|u| if u.ready { Some(u.id) } else { None }) + .collect(), + ) + } + + /// Try to start a game, if the user can and everybody is ready + pub fn start(&mut self, user: UserId) -> Result<(), LobbyErr> { + if let Some(_) = self + .inner + .players + .iter() + .filter(|u| u.admin) + .find(|u| u.id == user) + { + return Err(LobbyErr::NotAuthorized); + }; + + match self + .inner + .players + .iter() + .filter(|u| !u.ready) + .collect::>() + .len() + { + 0 => Err(LobbyErr::NotAllReady), + _ => Ok(()), + } + } +} diff --git a/games/rstnode/rst-core/src/map.rs b/games/rstnode/rst-core/src/map.rs new file mode 100644 index 000000000000..37f758b4a433 --- /dev/null +++ b/games/rstnode/rst-core/src/map.rs @@ -0,0 +1,80 @@ +//! Implements a map graph and world logic + +use crate::{ + config::{LinkCfg, MapCfg, NodeCfg}, + data::{Link, Node, NodeId}, + server::{ServerErr, ServerResult}, + wire::Response, +}; +use async_std::sync::Arc; +use quadtree_rs::{ + area::{Area, AreaBuilder}, + point::Point, + Quadtree, +}; +use std::collections::BTreeMap; + +pub struct MapNode { + pub pos: (f64, f64), + pub inner: Node, +} + +/// A map that people fight on +/// +/// A map is defined by it's graph relationships, but also where on +/// the map nodes are placed, how much spacing there is, etc. All +/// this information is encoded in the same structs because it's +/// static, and just more convenient. +pub struct Map { + /// Node IDs mapped to coordinates + nodes: BTreeMap, + /// Link IDs mapped to link objects + links: BTreeMap>, + /// A coordinate map for the network + coord: Quadtree>, +} + +impl Map { + pub fn new() -> Self { + Self { + nodes: BTreeMap::new(), + links: BTreeMap::new(), + coord: Quadtree::new(2), + } + } + + pub fn update(&mut self, cb: F) -> ServerResult + where + F: Fn(&mut Map) -> ServerResult, + { + unimplemented!() + } + + /// Get all objects that can be selected by a single point + pub fn get_by_point(&self, x: i64, y: i64) -> Option> { + self.coord + .query( + AreaBuilder::default() + .anchor(Point::from((x, y))) + .dimensions((1, 1)) + .build() + .ok()?, + ) + .map(|entry| Some(entry.value_ref().inner.id)) + .collect() + } + + /// Get all objects that can be selected by a 2d area + pub fn get_by_area(&self, x: i64, y: i64, w: i64, h: i64) -> Option> { + self.coord + .query( + AreaBuilder::default() + .anchor(Point::from((x, y))) + .dimensions((w, h)) + .build() + .ok()?, + ) + .map(|entry| Some(entry.value_ref().inner.id)) + .collect() + } +} diff --git a/games/rstnode/rst-core/src/mapstore.rs b/games/rstnode/rst-core/src/mapstore.rs new file mode 100644 index 000000000000..85c5e36ef93b --- /dev/null +++ b/games/rstnode/rst-core/src/mapstore.rs @@ -0,0 +1,17 @@ +//! Map store + +use crate::config::MapCfg; +use std::{collections::BTreeMap, fs, path::Path}; + +pub struct MapStore { + configs: BTreeMap, +} + +impl MapStore { + /// Load a set of map configs + pub fn load_path(&mut self, path: &Path) { + fs::read_dir(&path).unwrap().for_each(|d| { + let name = d.unwrap().file_name().into_string().unwrap(); + }); + } +} diff --git a/games/rstnode/rst-core/src/runner.rs b/games/rstnode/rst-core/src/runner.rs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/games/rstnode/rst-core/src/runner.rs @@ -0,0 +1 @@ + diff --git a/games/rstnode/rst-core/src/server.rs b/games/rstnode/rst-core/src/server.rs new file mode 100644 index 000000000000..3d95c3638c98 --- /dev/null +++ b/games/rstnode/rst-core/src/server.rs @@ -0,0 +1,155 @@ +//! Game server state handler +//! +//! A server can host many lobbies at the same time. It listens for +//! connections according to a address given to the initialiser. + +use crate::{ + _if::GameIf, + _match::Match, + data::Player, + lobby::LobbyList, + map::Map, + users::UserStore, + wire::{ + Action, AuthErr, Lobby, LobbyErr, LobbyId, LobbyUpdate, MatchErr, MatchId, RegErr, + Response, UpdateState, User, UserId, + }, +}; +use async_std::sync::{Arc, Mutex, RwLock}; +use async_trait::async_trait; +use chrono::{DateTime, Utc}; +use std::{collections::BTreeMap, path::Path}; + +/// A convenience result wrapper for server actions +pub type ServerResult = Result; +pub enum ServerErr { + /// The requested directory is corrupted + NoSuchDir, + /// Corrupted game state + Corrupted, + /// No such match found + NoSuchMatch, +} + +/// The game's server backend +pub struct Server { + matches: BTreeMap>, + users: UserStore, + lobbies: LobbyList, +} + +impl Server { + /// Create a new game server + fn new() -> Self { + Self { + matches: Default::default(), + users: UserStore::new(), + lobbies: LobbyList::new(), + } + } + + /// Open the state dir of a game server + pub async fn open(self: Arc, path: &Path) -> ServerResult<()> { + Ok(()) + } + + /// Stop accepting new game connections and shutdown gracefully + /// + /// Returns the number of matches still going on. + pub async fn shutdown(self: Arc) -> ServerResult { + Ok(0) + } + + /// Save and close the statedir and kicking all players + /// + /// Returns the number of players that were kicked off the server + /// prematurely. + pub async fn kill(self: Arc) -> ServerResult { + Ok(0) + } + + pub async fn update_map(self: Arc, id: MatchId, cb: F) -> ServerResult + where + F: Fn(&mut Map) -> ServerResult, + { + match self.matches.get(&id) { + Some(ref m) => m.lock().await.map.update(cb), + None => Err(ServerErr::NoSuchMatch), + } + } + + pub async fn update_players(self: Arc, id: MatchId, cb: F) -> ServerResult + where + F: Fn(&mut Vec) -> ServerResult, + { + match self.matches.get(&id) { + Some(ref mut m) => cb(&mut m.lock().await.players), + None => Err(ServerErr::NoSuchMatch), + } + } +} + +#[async_trait] +impl GameIf for Server { + async fn register(self: Arc, name: String, pw: String) -> Result { + unimplemented!() + } + + async fn login(self: Arc, name: String, pw: String) -> Result { + unimplemented!() + } + + async fn logout(self: Arc, user: User) -> Result<(), AuthErr> { + unimplemented!() + } + + async fn anonymous(self: Arc, name: String) -> Result { + let (_, auth) = self.users.add(name, None, true).await; + Ok(auth) + } + + async fn join(self: Arc, user: User, lobby: LobbyId) -> Result { + let mu = self.users.get(&user).await?; + self.lobbies.get_mut(lobby, |mut l| l.join(&mu)).await + } + + async fn leave(self: Arc, user: User, lobby: LobbyId) -> Result<(), LobbyErr> { + let mu = self.users.get(&user).await?; + self.lobbies.get_mut(lobby, |mut l| l.leave(&mu)).await + } + + async fn ready( + self: Arc, + user: User, + lobby: LobbyId, + ready: bool, + ) -> Result { + self.lobbies + .get_mut(lobby, |mut l| l.ready(user, ready)) + .await + } + + /// A start request was received + async fn start_req( + self: Arc, + user: UserId, + lobby: LobbyId, + ) -> Result, LobbyErr> { + self.lobbies.get_mut(lobby, |mut l| l.start(user)).await?; + let lob = self.lobbies.consume(lobby).await?; + Ok(Utc::now()) + } + + async fn perform_action( + self: Arc, + user: User, + mtch: MatchId, + act: Action, + ) -> UpdateState { + unimplemented!() + } + + async fn leave_match(self: Arc, user: User, mtch: MatchId) -> Result<(), MatchErr> { + unimplemented!() + } +} diff --git a/games/rstnode/rst-core/src/stats.rs b/games/rstnode/rst-core/src/stats.rs new file mode 100644 index 000000000000..1c651a98a0a8 --- /dev/null +++ b/games/rstnode/rst-core/src/stats.rs @@ -0,0 +1,183 @@ +//! This file contains balancing data +//! +//! Each type of node and packet is being balanced for different +//! factors. The stats are just returned by these functions. +//! Whenever there is some effect that can happen in the game, it's +//! value (strength) will be derived from here. + +pub type Money = usize; + +/// The cost of doing business +pub mod costs { + use super::Money; + use crate::data::{Level, PacketType, Upgrade}; + + /// Takes the current node and desired upgrade level + pub fn upgrade(curr: &Upgrade, want: &Upgrade) -> Money { + use self::{Level::*, Upgrade::*}; + match (curr, want) { + // Base upgrades + (Base, Guard(One)) => 30, + (Base, Compute(One)) => 25, + (Base, Relay(One)) => 20, + + // Guards + (Guard(One), Guard(Two)) => 50, + (Guard(Two), Guard(Three)) => 75, + + // Compute is expensive + (Compute(One), Compute(Two)) => 50, + (Compute(Two), Compute(Three)) => 95, + + // Relays + (Relay(One), Relay(Two)) => 35, + (Relay(Two), Relay(Three)) => 55, + + // Can't touch this + (_, _) => unreachable!(), + } + } + + /// Sending certain packets costs money, let's find out how much + pub fn packets(node: &Upgrade, packet: &PacketType) -> Money { + use { + Level::*, + PacketType::*, + Upgrade::{Base, Compute as Cc, Guard, Relay}, + }; + match (node, packet) { + // Sending pings is free forever + (_, Ping) => 0, + // Capture packets always cost the same + (_, Capture) => 15, + + // The cost of compute packets increases with levels + // because their efficiency increases more dramatically + (Cc(One), Compute { .. }) => 7, + (Cc(Two), Compute { .. }) => 14, + (Cc(Three), Compute { .. }) => 21, + + // Payloads can only be sent from guards + (Guard(_), Payload { .. }) => 12, + + // Resets are relatively cheap + (Guard(One), Reset) => 16, + (Guard(Two), Reset) => 32, + (Guard(Three), Reset) => 48, + + (Guard(One), CNS) => 22, + (Guard(Two), CNS) => 40, + (Guard(Three), CNS) => 82, + + (Guard(Two), Nitm) => 64, + (Guard(Three), Nitm) => 148, + + (Guard(Two), Virus) => 40, + (Guard(Three), Virus) => 60, + + // Only level 3 guards can send takeovers + (Guard(Three), TakeOver) => 256, + + // Can't touch this + (_, _) => unreachable!(), + } + } +} + +/// This is what capitalists are all the rage about +pub mod gains { + use super::Money; + use crate::data::{Level, PacketType, Upgrade}; + use std::sync::atomic::Ordering; + + /// This will tell you if you'll receive any money this week + pub fn parse_packet(node: &Upgrade, packet: &PacketType) -> Money { + use { + Level::*, + PacketType::*, + Upgrade::{Base, Compute as Cc, Guard, Relay}, + }; + + /// A utility function which increments the packet progress + /// + /// Depending on the node that is processing the incoming + /// packet, progress is either stepped by 1, 1.5, or 2 times + /// the advertised step rate, which is set by the spawning + /// node of the compute job. This means that stronger compute + /// nodes have a positive impact even in mostly low-level + /// systems. + /// + /// This function returns if it reached or overflowed the max + fn incr_compute(l: &Level, packet: &PacketType) -> bool { + match (l, packet) { + (lvl, Compute { max, curr, step }) => { + if curr.load(Ordering::Relaxed) < *max { + curr.fetch_add( + match lvl { + One => *step, + Two => (*step as f32 * 1.5) as u16, + Three => *step * 2, + }, + Ordering::Relaxed, + ); + } + + // Did we reach the target? + curr.load(Ordering::Relaxed) >= *max + } + (_, _) => unreachable!(), + } + } + + match (node, packet) { + // A basic income for all node and packets + (Base, Ping) => 4, + (Guard(One), Ping) | (Cc(One), Ping) | (Relay(One), Ping) => 6, + (Guard(Two), Ping) | (Cc(Two), Ping) | (Relay(Two), Ping) => 11, + (Guard(Three), Ping) | (Cc(Three), Ping) | (Relay(Three), Ping) => 17, + + // A compute node will always increment the packet + // reference. If it made it to "max", it will then also + // return an amount of money that can be gained + (Cc(ref lvl), Compute { ref max, .. }) if incr_compute(lvl, packet) => match lvl { + One => *max as usize, + Two => (max * 2) as usize, + Three => (max * 3) as usize, + }, + + // If in doubt, nada! + (_, _) => 0, + } + } +} + +pub mod strengths { + use crate::data::Level::{self, *}; + + /// Determine the maximum amount of resources for a compute packet + pub fn compute_max(lvl: Level) -> u16 { + match lvl { + One => 65, + Two => 120, + Three => 250, + } + } + + /// Determine the step size by which a computation advances + pub fn compute_step(lvl: Level) -> u16 { + match lvl { + One => 7, + Two => 20, + Three => 32, + } + } + + /// Determine the reward gained from processing a payload packet + pub fn payload_reward(lvl: Level) -> u16 { + match lvl { + One => 70, + Two => 150, + Three => 275, + } + } +} diff --git a/games/rstnode/rst-core/src/users.rs b/games/rstnode/rst-core/src/users.rs new file mode 100644 index 000000000000..0c93b83ec1da --- /dev/null +++ b/games/rstnode/rst-core/src/users.rs @@ -0,0 +1,69 @@ +//! A users abstraction module + +use crate::{ + wire::{LobbyErr, User, UserId}, + Id, +}; +use async_std::sync::{Arc, RwLock}; +use std::{ + collections::BTreeMap, + sync::atomic::{AtomicUsize, Ordering}, +}; + +pub struct MetaUser { + pub id: UserId, + pub name: String, + pub pw: String, + pub auth: User, +} + +pub struct UserStore { + max: AtomicUsize, + users: RwLock>>, +} + +impl UserStore { + /// Currently resuming a userstore isn't possible + pub fn new() -> Self { + UserStore { + max: 0.into(), + users: Default::default(), + } + } + + /// Get the metadata user for a login user + pub async fn get(&self, user: &User) -> Result, LobbyErr> { + match self.users.read().await.get(&user.id) { + Some(ref u) => Ok(Arc::clone(u)), + None => Err(LobbyErr::OtherError), + } + } + + pub async fn add>>( + &self, + name: String, + pw: S, + registered: bool, + ) -> (UserId, User) { + let id = self.max.fetch_add(1, Ordering::Relaxed); + let token = Id::random(); + let pw = pw.into().unwrap_or("".into()); + let auth = User { + id, + token, + registered, + }; + + self.users.write().await.insert( + id, + MetaUser { + id, + name, + pw, + auth: auth.clone(), + } + .into(), + ); + (id, auth.clone()) + } +} diff --git a/games/rstnode/rst-core/src/wire/action.rs b/games/rstnode/rst-core/src/wire/action.rs new file mode 100644 index 000000000000..22ab7ce6868e --- /dev/null +++ b/games/rstnode/rst-core/src/wire/action.rs @@ -0,0 +1,33 @@ +use crate::data::{NodeId, Upgrade}; +use serde::{Deserialize, Serialize}; + +/// All actions that a user can trigger via the UI +#[derive(Serialize, Deserialize)] +pub enum Action { + /// Cancel the running action + Cancel(NodeId), + /// Start a capture action + Capture { from: NodeId, to: NodeId }, + /// Set the compute targets + Compute { from: NodeId, to: Vec }, + /// Set to payload analysis mode + Payload(NodeId), + /// Send an exploit across the network + Reset { + from: NodeId, + to: NodeId, + exp: Exploit, + }, + /// Try to upgrade the node to a level + Upgrade { node: NodeId, level: Upgrade }, +} + +/// A type of exploit a node can start running +#[derive(Serialize, Deserialize)] +pub enum Exploit { + Reset, + CNS, + Nitm, + Virus, + TakeOver, +} diff --git a/games/rstnode/rst-core/src/wire/mod.rs b/games/rstnode/rst-core/src/wire/mod.rs new file mode 100644 index 000000000000..cd311383a83a --- /dev/null +++ b/games/rstnode/rst-core/src/wire/mod.rs @@ -0,0 +1,68 @@ +//! Network formats and container messages + +mod action; +pub use action::*; + +mod req; +pub use req::*; + +mod resp; +pub use resp::*; + +mod update; +pub use update::*; + +use crate::{ + data::{Color, Player}, + map::Map, + Id, +}; +use serde::{Deserialize, Serialize}; + +/// An alias for a User's ID +pub type UserId = usize; + +/// Represents a user payload +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct User { + /// The internal user ID + pub id: UserId, + /// The auth token provided by the client + pub token: Id, + /// Whether the scores will be tracked + pub registered: bool, +} + +/// A more lobby specific abstraction for a user +#[derive(Clone, Serialize, Deserialize)] +pub struct LobbyUser { + /// The user ID + pub id: UserId, + /// Their nick name + pub name: String, + /// Are they ready? + pub ready: bool, + /// Are they the lobby admin? + pub admin: bool, + /// The colour they will be in the match + pub color: Color, +} + +/// An alias for a Room ID +pub type LobbyId = usize; + +/// Represent a lobby +#[derive(Clone, Serialize, Deserialize)] +pub struct Lobby { + /// The ID of the lobby + pub id: LobbyId, + /// A set of user IDs + pub players: Vec, + /// The name of the map + pub map: String, + /// Settings + pub settings: Vec, +} + +/// An alias for a match ID +pub type MatchId = usize; diff --git a/games/rstnode/rst-core/src/wire/req.rs b/games/rstnode/rst-core/src/wire/req.rs new file mode 100644 index 000000000000..ffefcbdb6ac7 --- /dev/null +++ b/games/rstnode/rst-core/src/wire/req.rs @@ -0,0 +1,38 @@ +use super::{action::Action, LobbyId, MatchId, User}; +use serde::{Deserialize, Serialize}; + +/// A message sent from the game client to the server +#[derive(Serialize, Deserialize)] +pub enum Request { + /// Register yourself with the game server + Register(String, String), + + /// Login to your user session + /// + /// This user can't log into the system from another computer + Login(String, String), + + /// Close your user session + Logout(User), + + /// Start an anonymous session + Anonymous(String), + + /// A user joins a game lobby + Join(User, LobbyId), + + /// A user leaves a game lobby + Leave(User, LobbyId), + + /// Mark a user as ready + Ready(User, LobbyId, bool), + + /// Try to start the match + StartReq(User, LobbyId), + + /// Send a move in the game + GameAction(User, MatchId, Action), + + /// Leave the match (forfeit) + LeaveGame(User, MatchId), +} diff --git a/games/rstnode/rst-core/src/wire/resp.rs b/games/rstnode/rst-core/src/wire/resp.rs new file mode 100644 index 000000000000..ef2e192a6044 --- /dev/null +++ b/games/rstnode/rst-core/src/wire/resp.rs @@ -0,0 +1,94 @@ +//! Response values that the server can reply with + +use super::{Lobby, LobbyId, User, UserId}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// A response values from the server +#[derive(Serialize, Deserialize)] +pub enum Response { + /// Response to the register request + Register(Result), + /// Response to login request + Login(Result), + /// Response to login request + Logout(Result<(), AuthErr>), + /// Get a list of available pairs + Rooms(Vec<(String, LobbyId)>), + /// A user joins a game lobby + Join(Result), + /// A user leaves a game lobby + Leave(Result<(), LobbyErr>), + /// Get the new set of ready states + Ready(LobbyUpdate), + /// Receiving a start request time + StartReq(DateTime), + /// Response to the action with the state update + GameUpdate(UpdateState), + /// Leave the match (forfeit) + LeaveGame(Result<(), MatchErr>), +} + +#[derive(Serialize, Deserialize)] +pub enum RegErr { + /// The password is way too bad + BadPassword, + /// THe username is already taken + UsernameTaken, + /// Other internal error, try again? + OtherError, +} + +#[derive(Serialize, Deserialize)] +pub enum AuthErr { + /// Wrong password for the user + WrongPassword, + /// The requested user doesn't exist + UserNotFound, + /// No session currently exists + NoSossion, + /// Other internal error, try again? + OtherError, +} + +#[derive(Serialize, Deserialize)] +pub enum LobbyErr { + /// The requested room is already full + RoomFull, + /// The room id is unknown + NoSuchRoom, + /// Previously not in room + NotInRoom, + /// Not everybody was ready + NotAllReady, + /// A request was sent by someone who isn't authorised + NotAuthorized, + /// Other internal error, try again? + OtherError, +} + +#[derive(Serialize, Deserialize)] +pub enum LobbyUpdate { + /// The set of ready users + Ready(Vec), +} + +/// The way the update was applied +#[derive(Serialize, Deserialize)] +pub enum UpdateState { + /// The update was applied seamlessly + Success, + /// The update was inserted, but had to be re-ordered with another update + Reordered, + /// The sent request was invalid and was not applied + Invalid, +} + +/// An error that can occur in a match +#[derive(Serialize, Deserialize)] +pub enum MatchErr { + /// The provided player wasn't in the match (anymore?) + NotInMatch, + /// The requested match had already ended + MatchAlreadyEnded, +} diff --git a/games/rstnode/rst-core/src/wire/update.rs b/games/rstnode/rst-core/src/wire/update.rs new file mode 100644 index 000000000000..a1b47ff07e50 --- /dev/null +++ b/games/rstnode/rst-core/src/wire/update.rs @@ -0,0 +1,72 @@ +//! Update to the game state + +use super::UserId; +use crate::data::{NodeId, PacketId, Player, Upgrade}; +use serde::{Deserialize, Serialize}; + +/// An update provided by the game server +#[derive(Serialize, Deserialize)] +pub enum Update { + /// Update made to a node + Node(NodeUpdate), + /// Update made to a link + Link(LinkUpdate), + /// Update made to a packet + Packet(PacketUpdate), + /// Update made to the user set + User(UserUpdate), + /// An error occured, can be non-fatal + Error(UpdateError), +} + +/// Update made to a node +#[derive(Serialize, Deserialize)] +pub enum NodeUpdate { + /// The node owner changed + Owner(Player), + /// Represent a new upgrade state + Level { node: NodeId, new: Upgrade }, + /// A new packet was consumed from a link + NewPacket(PacketId), + /// Remove a packet from the node buffer + SentPacket(PacketId), + /// Dropped a packet + DropPacket(PacketId), +} + +/// Update made to a link +#[derive(Serialize, Deserialize)] +pub enum LinkUpdate { + /// Take a packet from a node's buffer + TakePacket(PacketId), + /// Give a packet to a node's buffer + GivePacket(PacketId), +} + +/// Update made to a packet +#[derive(Serialize, Deserialize)] +pub enum PacketUpdate { + /// Advance a packet along one step along the link + Increment(PacketId), +} + +/// Update made to the user set +#[derive(Serialize, Deserialize)] +pub enum UserUpdate { + UserLeft(UserId), +} + +/// An error occured, can be non-fatal +#[derive(Serialize, Deserialize)] +pub enum UpdateError { + /// You are the last user in the match + LastUser, + /// The game crashed, so kick all + GameCrashed, + /// The server's time was behind the client time + /// + /// This means that newer events will be dropped from the map + /// state. This should prompt the client to warn the user this + /// has happened, then resync the time of the game states. + TimeAheadServer, +} diff --git a/games/rstnode/rst-launcher/Cargo.toml b/games/rstnode/rst-launcher/Cargo.toml new file mode 100644 index 000000000000..3df25c561eb1 --- /dev/null +++ b/games/rstnode/rst-launcher/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rst-launcher" +version = "0.1.0" +authors = ["Katharina Fey "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/games/rstnode/rst-launcher/src/main.rs b/games/rstnode/rst-launcher/src/main.rs new file mode 100644 index 000000000000..e7a11a969c03 --- /dev/null +++ b/games/rstnode/rst-launcher/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/games/rstnode/rst-server/Cargo.toml b/games/rstnode/rst-server/Cargo.toml new file mode 100644 index 000000000000..865caf9fab15 --- /dev/null +++ b/games/rstnode/rst-server/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rst-node-server" +description = "Backend game server" +version = "0.0.0" +edition = "2018" +license = "AGPL-3.0-or-later" +authors = ["Bread Machine", "Katharina Fey "] + +[dependencies] +rst-core = { path = "../rst-core" } \ No newline at end of file diff --git a/games/rstnode/rst-server/src/main.rs b/games/rstnode/rst-server/src/main.rs new file mode 100644 index 000000000000..e7a11a969c03 --- /dev/null +++ b/games/rstnode/rst-server/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/games/rstnode/shell.nix b/games/rstnode/shell.nix index 0cf2e03db1ec..98465394e67e 100644 --- a/games/rstnode/shell.nix +++ b/games/rstnode/shell.nix @@ -3,8 +3,8 @@ with import {}; stdenv.mkDerivation { name = "rstnode"; buildInputs = with pkgs; [ - rustc cargo clangStdenv stdenv rust-analyzer - alsaLib pkg-config libudev steam-run + rustc cargo clangStdenv rust-analyzer rustfmt + alsaLib pkg-config libudev ] ++ (with pkgs.xlibs; [ libGL libGLU diff --git a/games/rstnode/src/_if.rs b/games/rstnode/src/_if.rs deleted file mode 100644 index 1a842840708c..000000000000 --- a/games/rstnode/src/_if.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! A common trait interface between the server and the client - -use crate::wire::{ - Action, AuthErr, Lobby, LobbyErr, LobbyId, LobbyUpdate, MatchErr, MatchId, RegErr, UpdateState, - User, UserId, -}; -use async_std::sync::Arc; -use async_trait::async_trait; -use chrono::{DateTime, Utc}; - -/// Main game interface implemented by the server and client -/// -/// The client implementation simply translates requests to network -/// requests that are sent to the server. The server implementation -/// consists of two parts: the network layer listening loop, and the -/// game server state which then implements the actual game logic. -#[async_trait] -pub trait GameIf { - /// Register a new user on a game server - async fn register(self: Arc, name: String, pw: String) -> Result; - - /// Login for an existing user - async fn login(self: Arc, name: String, pw: String) -> Result; - - /// End a user session (go offline) - async fn logout(self: Arc, user: User) -> Result<(), AuthErr>; - - /// Register as an anonymous player - async fn anonymous(self: Arc, name: String) -> Result; - - /// Join a match-making lobby - async fn join(self: Arc, user: User, lobby: LobbyId) -> Result; - - /// Leave a match-making lobby - async fn leave(self: Arc, user: User, lobby: LobbyId) -> Result<(), LobbyErr>; - - /// Set the player's ready state - async fn ready( - self: Arc, - user: User, - lobby: LobbyId, - ready: bool, - ) -> Result; - - /// Send a start request (as lobby admin) - async fn start_req( - self: Arc, - user: UserId, - lobby: LobbyId, - ) -> Result, LobbyErr>; - - /// Perform a game action as a user - async fn perform_action(self: Arc, user: User, mtch: MatchId, act: Action) - -> UpdateState; - - /// Leave a match - async fn leave_match(self: Arc, user: User, mtch: MatchId) -> Result<(), MatchErr>; -} diff --git a/games/rstnode/src/_loop.rs b/games/rstnode/src/_loop.rs deleted file mode 100644 index 862325490322..000000000000 --- a/games/rstnode/src/_loop.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! A timed loop implementation - -use async_std::{future::Future, task}; -use chrono::{DateTime, Utc}; -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Duration, -}; - -/// Number of ticks per second -#[from_env("RSTNODE_TICKS")] -const TICKS: u64 = 100; -const TICK_TIME: Duration = Duration::from_millis(1000 / TICKS); - -/// Run a timed loop until you no longer want to -pub fn block_loop(run: Arc, f: F) -where - F: Future + Send + Copy + 'static, -{ - while run.load(Ordering::Relaxed) { - let t1 = Utc::now(); - task::block_on(f); - let t2 = Utc::now(); - let t3 = (t2 - t1).to_std().unwrap(); - task::block_on(async { task::sleep(TICK_TIME - t3) }); - } -} - -/// Run a detached timed loop until you no longer want to -pub fn spawn_loop(run: Arc, f: F) -where - F: Future + Send + Copy + 'static, -{ - task::spawn(async move { block_loop(run, f) }); -} diff --git a/games/rstnode/src/_match.rs b/games/rstnode/src/_match.rs deleted file mode 100644 index 6ae69c54e935..000000000000 --- a/games/rstnode/src/_match.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{ - data::Player, - lobby::MetaLobby, - map::Map, - wire::{Action, LobbyUser, MatchId, UserId}, -}; -use async_std::sync::{Arc, RwLock}; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; - -/// Describes a match for the server -pub struct Match { - /// The match id - pub id: MatchId, - /// The list of active players - pub players: Vec, - /// The active game map - pub map: Map, - /// Input inbox (handled in-order each game tick) - inbox: RwLock>, - /// The time the match was initialised - init_t: DateTime, - /// The synced time the match was started - start_t: Option>, -} - -impl From for Match { - fn from(ml: MetaLobby) -> Self { - Self { - id: ml.inner.id, - players: ml - .inner - .players - .into_iter() - .map(|lu| Player { - id: lu.id, - name: lu.name, - color: lu.color, - money: 0.into(), - }) - .collect(), - map: Map::new(), - inbox: Default::default(), - init_t: Utc::now(), - start_t: None, - } - } -} - -impl Match { - /// Set the start time of the match, which may be in the future - pub fn set_start(&mut self, t: DateTime) { - self.start_t = Some(t); - } - - /// Queue a new game action - pub async fn queue(&self, cmd: Action) { - self.inbox.write().await.push_back(cmd); - } - - pub async fn handle_inbox(&mut self) { - // for act in self.inbox.write().await.drain() { - - // } - } -} diff --git a/games/rstnode/src/bin/game.rs b/games/rstnode/src/bin/game.rs deleted file mode 100644 index 9bf9fc9f9631..000000000000 --- a/games/rstnode/src/bin/game.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! The main game UI client - -fn main() { - -} diff --git a/games/rstnode/src/bin/server.rs b/games/rstnode/src/bin/server.rs deleted file mode 100644 index 47db85926ac1..000000000000 --- a/games/rstnode/src/bin/server.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! The dedicated server binary - - -fn main() { - -} diff --git a/games/rstnode/src/config.rs b/games/rstnode/src/config.rs deleted file mode 100644 index 8a1b0b4deb0a..000000000000 --- a/games/rstnode/src/config.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! The file formats backing maps and other configs - -use crate::data::{NodeId, LinkId}; -use serde::{Serialize, Deserialize}; -use std::path::Path; - -/// A config tree that describes a map -#[derive(Serialize, Deserialize)] -pub struct MapCfg { - /// The set of nodes - pub nodes: Vec, - /// Links connecting nodes - pub links: Vec, - /// Default spawn points (player count) - pub spawns: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct NodeCfg { - /// Node ID - pub id: NodeId, - /// Render/world position - pub x: f64, - pub y: f64, -} - -#[derive(Serialize, Deserialize)] -pub struct LinkCfg { - /// The link ID - id: LinkId, - /// List of connectioned nodes - con: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct SpawnCfg { - /// The node of the spawn point - pub n_id: NodeId, - /// At what number of players is this spawn available? - pub max_players: usize, -} diff --git a/games/rstnode/src/data.rs b/games/rstnode/src/data.rs deleted file mode 100644 index 5ce5517c2b07..000000000000 --- a/games/rstnode/src/data.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! Data structures for the game - -use crate::io::Io; -use async_std::sync::Arc; -use rand::seq::SliceRandom; -use rand::thread_rng; -use serde::{Deserialize, Serialize}; -use std::{ - collections::BTreeMap, - sync::atomic::{AtomicBool, AtomicU16, AtomicU32}, -}; - -pub type NodeId = usize; - -/// A node is a computer on the network graph -/// -/// It's owned by a player, and has some upgrade state, as well as -/// base stats. -#[derive(Serialize, Deserialize)] -pub struct Node { - /// Each node has a unique ID by which it's addressed - pub id: NodeId, - /// The current health - pub health: AtomicU32, - /// The max health - pub max_health: AtomicU32, - /// The owner of this node - pub owner: Owner, - /// Upgrade state - pub type_: Upgrade, - /// Number of links on the map - pub links: u8, - /// Active link states - pub link_states: Vec>, - /// Input buffer - #[serde(skip)] - pub buffer: Vec, -} - -pub type LinkId = usize; - -/// A one-to-one link between two nodes -#[derive(Serialize, Deserialize)] -pub struct Link { - /// This link ID - id: LinkId, - /// Node 1 - a: NodeId, - /// Node 2 - b: NodeId, - /// The step length - length: usize, - /// Packets present on this link - #[serde(skip)] - pp: Vec<(Packet, AtomicU32)>, - /// Actual Rx, Tx pair - #[serde(skip)] - io: Io, -} - -pub type PacketId = usize; - -/// A packet going across the network -pub struct Packet { - /// The packet ID - id: PacketId, - /// Declare this packet to be removed - dead: AtomicBool, - /// Each packet is owned by a player - owner: Arc, - /// What type of packet this is - data_: PacketType, -} - -pub type PlayerId = usize; - -/// A player who's having fun -#[derive(Serialize, Deserialize)] -pub struct Player { - /// A unique player ID (per match) - pub id: PlayerId, - /// The player name - pub name: String, - /// Player color - pub color: Color, - /// The player's money - pub money: AtomicU16, -} - -/// Optionally, players can create teams -#[derive(Serialize, Deserialize)] -pub struct Team { - /// Name of the team - name: String, - /// Unified color of the team - color: Color, - /// All team members by their ID - roster: Vec, -} - -/// An RGB color without alpha -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Color(u8, u8, u8); - -impl Color { - pub fn black() -> Self { - Self(50, 50, 50) - } - - pub fn red() -> Self { - Self(250, 50, 50) - } - - pub fn green() -> Self { - Self(100, 250, 100) - } - - pub fn blue() -> Self { - Self(100, 100, 250) - } - - pub fn teal() -> Self { - Self(150, 250, 250) - } - - pub fn purple() -> Self { - Self(150, 100, 250) - } - - pub fn orange() -> Self { - Self(250, 200, 100) - } - - pub fn yellow() -> Self { - Self(250, 250, 100) - } - - pub fn white() -> Self { - Self(225, 225, 225) - } -} - -pub trait ColorPalette { - /// Create a new color palette - fn palette() -> Self; - /// Get a palette without a certain colour - fn without(&mut self, b: &Color); - /// Mix a color back into the available palette - fn remix(&mut self, new: Color); -} - -impl ColorPalette for Vec { - fn palette() -> Self { - let mut rng = thread_rng(); - let mut pal = vec![ - Color::black(), - Color::red(), - Color::green(), - Color::blue(), - Color::teal(), - Color::purple(), - Color::orange(), - Color::yellow(), - Color::white(), - ]; - pal.shuffle(&mut rng); - pal - } - - /// Drop a colour from the palette - fn without(&mut self, b: &Color) { - if let Some(pos) = self.into_iter().rposition(|a| a == b) { - self.remove(pos); - } - } - - fn remix(&mut self, new: Color) { - let mut rng = thread_rng(); - self.push(new); - self.shuffle(&mut rng); - } -} - -/// Describes ownership state -#[derive(Serialize, Deserialize)] -pub enum Owner { - /// Nobody owns this - Neutral, - /// A player owns this - Player(Player), -} - -/// Encodes upgrade level without numbers -#[derive(Copy, Clone, Serialize, Deserialize)] -pub enum Level { - /// 1 - One, - /// 2 - Two, - /// 3 (wow) - Three, -} - -/// Describes upgrade state -#[derive(Copy, Clone, Serialize, Deserialize)] -pub enum Upgrade { - /// A basic node - Base, - /// Battle (attack/defence) nodes - Guard(Level), - /// These nodes make money - Compute(Level), - /// Good at packet switching - Relay(Level), -} - -/// Possible types of packets -pub enum PacketType { - /// A keepalive packet - /// - /// These are sent by all nodes if their neighbours are either - /// friendly or neutral, and used to keep the network alive. - /// Sending them costs nothing, and when they are received, they - /// yield a small amount of funds, and restoring health of a node. - Ping, - /// A non exploit capture - /// - /// This is a packet that can be sent out by any node (except a - /// switch) to claim a neutral node on the network. The path to - /// the node needs to consist only of friendlies, and if an enemy - /// node processes this type, nothing happens. - Capture, - /// A compute packet - /// - /// The first value is the target compute value, which each - /// compute node adds on to. The second value is the current. If - /// a compute packet passes through a compromised or enemy node, - /// it might subtract from the second value, before palling it on. - Compute { - max: u16, - curr: AtomicU16, - step: u16, - }, - /// A special wrapper packet generated by guards - /// - /// Sometimes, when a hostily attack packet encounters a guard, it - /// manages to capture the attack, and forwards it to a random - /// compute node. If the node manages to handle the packet, - /// without it getting dropped in the meantime), it yields a - /// specified reward, like a computation would. - Payload { inner: Box, reward: u16 }, - /// A reset attack packet - /// - /// When encountering a hostile node, it will make that node drop - /// all packets in it's buffers. - Reset, - /// Cross-node-scripting attack - /// - /// Decreases the strength of a node, also having a small chance - /// of spawning a new packet into a random output buffer. When - /// applied to a neutral node, it makes capturing nodes go faster. - CNS, - /// Node-in-the-middle attack - /// - /// Infect the routing behaviour of a node to route all traffic to - /// a specified enemy node instead - Nitm, - /// Virus infection attack - /// - /// Infects a node to capture it's earnings, both active and - /// passive, for a short time, without taking on it's costs. - Virus, - /// A total control exploit - /// - /// This is very hard to do, and a node will basically always - /// resist it, but if successful, transforms the node into a guard - /// node and yields control to the attackinng player. - TakeOver, -} diff --git a/games/rstnode/src/gens.rs b/games/rstnode/src/gens.rs deleted file mode 100644 index 7374731c826d..000000000000 --- a/games/rstnode/src/gens.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Helpers to determine if a node can send a particular packet. - -/// These functions are unfortunately all a bit stringly typed atm -pub mod can_send { - use crate::data::{Level, PacketType, Upgrade}; - use Level::*; - - #[inline] - pub fn base() -> Vec<&'static str> { - vec!["ping", "capture"] - } - - /// Guard nodes are the most versatile - #[inline] - pub fn guard(lvl: Level) -> Vec<&'static str> { - match lvl { - // This is just kinda gross - One => { - let mut p = base(); - p.append(&mut vec!["payload", "cns", "reset"]); - p - } - Two => { - let mut p = guard(One); - p.append(&mut vec!["nitm", "virus"]); - p - } - Three => { - let mut p = guard(Two); - p.append(&mut vec!["takeover"]); - p - } - } - } - - /// Compute nodes can ping and compute but not capture - pub fn compute() -> Vec<&'static str> { - vec!["ping", "compute"] - } - - /// Relays only relay! - pub fn relay() -> Vec<&'static str> { - vec![] - } -} - -pub mod send { - use crate::{ - data::{ - Level::*, - PacketType as Pkt, - Upgrade::{self, *}, - }, - stats::strengths, - }; - - /// Turn the string type identifier into a packet - /// - /// This function makes heavy use of the stats module, which is - /// responsible for balancing all of these values. - pub fn build_packet(node: Upgrade, type_: &'static str, prev: Option) -> Pkt { - match (prev, node, type_) { - // All pings are the same - (None, _, "ping") => Pkt::Ping, - - // TODO: captures should improve - (None, Base, "capture") | (None, Guard(_), "capture") => Pkt::Capture, - - (None, Compute(lvl), "compute") => Pkt::Compute { - curr: Default::default(), - max: strengths::compute_max(lvl), - step: strengths::compute_step(lvl), - }, - - (Some(prev), Guard(lvl), "payload") => Pkt::Payload { - inner: Box::new(prev), - reward: strengths::payload_reward(lvl), - }, - - (None, Guard(_), "cns") => Pkt::CNS, - (None, Guard(_), "reset") => Pkt::Reset, - (None, Guard(_), "nitm") => Pkt::Nitm, - (None, Guard(_), "virus") => Pkt::Virus, - - // Only level 3 guards can send takeovers - (None, Guard(Three), "takeover") => Pkt::TakeOver, - - // Can't touch this - (_, _, _) => unreachable!(), - } - } -} diff --git a/games/rstnode/src/io.rs b/games/rstnode/src/io.rs deleted file mode 100644 index 3032c2698190..000000000000 --- a/games/rstnode/src/io.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! A module that adapts the ratman layer to RstNode - -use netmod_mem::MemMod; - -/// A pair of exits, connected -pub struct Io { - a: MemMod, - b: MemMod, -} - -impl Io { - fn new() -> Self { - Self::default() - } -} - -impl Default for Io { - fn default() -> Self { - let (a, b) = MemMod::make_pair(); - Self { a, b } - } -} diff --git a/games/rstnode/src/lib.rs b/games/rstnode/src/lib.rs deleted file mode 100644 index e063da34d8bf..000000000000 --- a/games/rstnode/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![allow(warnings)] - -#[macro_use] -extern crate const_env; - -mod _if; -pub use _if::GameIf; - -pub mod data; -pub mod gens; - -pub mod _loop; -pub mod _match; -pub mod config; -pub mod io; -pub mod lobby; -pub mod map; -pub mod mapstore; -pub mod server; -pub mod stats; -pub mod users; -pub mod wire; - -pub use identity::Identity as Id; diff --git a/games/rstnode/src/lobby.rs b/games/rstnode/src/lobby.rs deleted file mode 100644 index 496f98bd7b6f..000000000000 --- a/games/rstnode/src/lobby.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! The code that handles the lobby logic - -use crate::{ - data::{Color, ColorPalette}, - users::MetaUser, - wire::{Lobby, LobbyErr, LobbyId, LobbyUpdate, LobbyUser, User, UserId}, -}; -use async_std::sync::{Arc, RwLock}; -use std::{ - collections::BTreeMap, - sync::atomic::{AtomicUsize, Ordering}, -}; - -/// A list of all the lobbies on the server -pub struct LobbyList { - max: AtomicUsize, - lobbies: RwLock>, -} - -impl LobbyList { - pub fn new() -> Self { - Self { - max: 0.into(), - lobbies: Default::default(), - } - } - - /// Create a new lobby - pub async fn create(&self, map: String) -> LobbyId { - let id = self.max.fetch_add(1, Ordering::Relaxed); - self.lobbies - .write() - .await - .insert(id, MetaLobby::create(id, map)); - id - } - - /// Remove a lobby by ID - pub async fn destroy(&self, id: LobbyId) -> Result<(), LobbyErr> { - self.consume(id).await.map(|_| ()) - } - - /// Remove and return the lobby - pub async fn consume(&self, id: LobbyId) -> Result { - self.lobbies - .write() - .await - .remove(&id) - .map_or(Err(LobbyErr::NoSuchRoom), |l| Ok(l)) - } - - /// Get mutable access to a lobby - pub async fn get_mut(&self, id: LobbyId, cb: F) -> Result - where - F: Fn(&mut MetaLobby) -> T, - { - self.lobbies - .write() - .await - .get_mut(&id) - .map_or(Err(LobbyErr::OtherError), |ref mut l| Ok(cb(l))) - } -} - -/// Additional state held by the server -/// -/// The meta lobby will also sync updates to all connected users, when updates are made to the lobby -pub struct MetaLobby { - pub palette: Vec, - pub inner: Lobby, -} - -impl MetaLobby { - pub fn create(id: LobbyId, map: String) -> Self { - Self { - palette: Vec::palette(), - inner: Lobby { - id, - map, - players: vec![], - settings: vec![], - }, - } - } - - pub fn join(&mut self, user: &MetaUser) -> Lobby { - let color = if &user.name == "spacekookie" { - let color = Color::blue(); - self.palette.without(&color); - - if let Some(user) = self - .inner - .players - .iter_mut() - .find(|u| u.color == Color::blue()) - { - user.color = self.palette.remove(0); - } - - color - } else { - self.palette.remove(0) - }; - - self.inner.players.push(LobbyUser { - admin: false, - id: user.id, - name: user.name.clone(), - ready: false, - color, - }); - - self.inner.clone() - } - - pub fn leave(&mut self, user: &MetaUser) { - let (pos, user) = self - .inner - .players - .iter() - .enumerate() - .find_map(|(num, u)| { - if u.id == user.id { - Some((num, u)) - } else { - None - } - }) - .unwrap(); - self.palette.remix(user.color); - self.inner.players.remove(pos); - } - - /// Check if a user is even present in a lobby - /// - /// Perform this prerequisite check before making other user-specific changes to the lobby - pub fn in_lobby(&self, user: UserId) -> bool { - self.inner - .players - .iter() - .find(|u| u.id == user) - .map(|_| true) - .unwrap_or(false) - } - - /// Set the ready state for a user - pub fn ready(&mut self, user: User, ready: bool) -> LobbyUpdate { - if let Some(user) = self - .inner - .players - .iter_mut() - .find(|u| u.id == user.id) - .as_mut() - { - user.ready = ready; - } - - LobbyUpdate::Ready( - self.inner - .players - .iter() - .filter_map(|u| if u.ready { Some(u.id) } else { None }) - .collect(), - ) - } - - /// Try to start a game, if the user can and everybody is ready - pub fn start(&mut self, user: UserId) -> Result<(), LobbyErr> { - if let Some(_) = self - .inner - .players - .iter() - .filter(|u| u.admin) - .find(|u| u.id == user) - { - return Err(LobbyErr::NotAuthorized); - }; - - match self - .inner - .players - .iter() - .filter(|u| !u.ready) - .collect::>() - .len() - { - 0 => Err(LobbyErr::NotAllReady), - _ => Ok(()), - } - } -} diff --git a/games/rstnode/src/map.rs b/games/rstnode/src/map.rs deleted file mode 100644 index 37f758b4a433..000000000000 --- a/games/rstnode/src/map.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Implements a map graph and world logic - -use crate::{ - config::{LinkCfg, MapCfg, NodeCfg}, - data::{Link, Node, NodeId}, - server::{ServerErr, ServerResult}, - wire::Response, -}; -use async_std::sync::Arc; -use quadtree_rs::{ - area::{Area, AreaBuilder}, - point::Point, - Quadtree, -}; -use std::collections::BTreeMap; - -pub struct MapNode { - pub pos: (f64, f64), - pub inner: Node, -} - -/// A map that people fight on -/// -/// A map is defined by it's graph relationships, but also where on -/// the map nodes are placed, how much spacing there is, etc. All -/// this information is encoded in the same structs because it's -/// static, and just more convenient. -pub struct Map { - /// Node IDs mapped to coordinates - nodes: BTreeMap, - /// Link IDs mapped to link objects - links: BTreeMap>, - /// A coordinate map for the network - coord: Quadtree>, -} - -impl Map { - pub fn new() -> Self { - Self { - nodes: BTreeMap::new(), - links: BTreeMap::new(), - coord: Quadtree::new(2), - } - } - - pub fn update(&mut self, cb: F) -> ServerResult - where - F: Fn(&mut Map) -> ServerResult, - { - unimplemented!() - } - - /// Get all objects that can be selected by a single point - pub fn get_by_point(&self, x: i64, y: i64) -> Option> { - self.coord - .query( - AreaBuilder::default() - .anchor(Point::from((x, y))) - .dimensions((1, 1)) - .build() - .ok()?, - ) - .map(|entry| Some(entry.value_ref().inner.id)) - .collect() - } - - /// Get all objects that can be selected by a 2d area - pub fn get_by_area(&self, x: i64, y: i64, w: i64, h: i64) -> Option> { - self.coord - .query( - AreaBuilder::default() - .anchor(Point::from((x, y))) - .dimensions((w, h)) - .build() - .ok()?, - ) - .map(|entry| Some(entry.value_ref().inner.id)) - .collect() - } -} diff --git a/games/rstnode/src/mapstore.rs b/games/rstnode/src/mapstore.rs deleted file mode 100644 index 85c5e36ef93b..000000000000 --- a/games/rstnode/src/mapstore.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Map store - -use crate::config::MapCfg; -use std::{collections::BTreeMap, fs, path::Path}; - -pub struct MapStore { - configs: BTreeMap, -} - -impl MapStore { - /// Load a set of map configs - pub fn load_path(&mut self, path: &Path) { - fs::read_dir(&path).unwrap().for_each(|d| { - let name = d.unwrap().file_name().into_string().unwrap(); - }); - } -} diff --git a/games/rstnode/src/runner.rs b/games/rstnode/src/runner.rs deleted file mode 100644 index 8b137891791f..000000000000 --- a/games/rstnode/src/runner.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/games/rstnode/src/server.rs b/games/rstnode/src/server.rs deleted file mode 100644 index 3d95c3638c98..000000000000 --- a/games/rstnode/src/server.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Game server state handler -//! -//! A server can host many lobbies at the same time. It listens for -//! connections according to a address given to the initialiser. - -use crate::{ - _if::GameIf, - _match::Match, - data::Player, - lobby::LobbyList, - map::Map, - users::UserStore, - wire::{ - Action, AuthErr, Lobby, LobbyErr, LobbyId, LobbyUpdate, MatchErr, MatchId, RegErr, - Response, UpdateState, User, UserId, - }, -}; -use async_std::sync::{Arc, Mutex, RwLock}; -use async_trait::async_trait; -use chrono::{DateTime, Utc}; -use std::{collections::BTreeMap, path::Path}; - -/// A convenience result wrapper for server actions -pub type ServerResult = Result; -pub enum ServerErr { - /// The requested directory is corrupted - NoSuchDir, - /// Corrupted game state - Corrupted, - /// No such match found - NoSuchMatch, -} - -/// The game's server backend -pub struct Server { - matches: BTreeMap>, - users: UserStore, - lobbies: LobbyList, -} - -impl Server { - /// Create a new game server - fn new() -> Self { - Self { - matches: Default::default(), - users: UserStore::new(), - lobbies: LobbyList::new(), - } - } - - /// Open the state dir of a game server - pub async fn open(self: Arc, path: &Path) -> ServerResult<()> { - Ok(()) - } - - /// Stop accepting new game connections and shutdown gracefully - /// - /// Returns the number of matches still going on. - pub async fn shutdown(self: Arc) -> ServerResult { - Ok(0) - } - - /// Save and close the statedir and kicking all players - /// - /// Returns the number of players that were kicked off the server - /// prematurely. - pub async fn kill(self: Arc) -> ServerResult { - Ok(0) - } - - pub async fn update_map(self: Arc, id: MatchId, cb: F) -> ServerResult - where - F: Fn(&mut Map) -> ServerResult, - { - match self.matches.get(&id) { - Some(ref m) => m.lock().await.map.update(cb), - None => Err(ServerErr::NoSuchMatch), - } - } - - pub async fn update_players(self: Arc, id: MatchId, cb: F) -> ServerResult - where - F: Fn(&mut Vec) -> ServerResult, - { - match self.matches.get(&id) { - Some(ref mut m) => cb(&mut m.lock().await.players), - None => Err(ServerErr::NoSuchMatch), - } - } -} - -#[async_trait] -impl GameIf for Server { - async fn register(self: Arc, name: String, pw: String) -> Result { - unimplemented!() - } - - async fn login(self: Arc, name: String, pw: String) -> Result { - unimplemented!() - } - - async fn logout(self: Arc, user: User) -> Result<(), AuthErr> { - unimplemented!() - } - - async fn anonymous(self: Arc, name: String) -> Result { - let (_, auth) = self.users.add(name, None, true).await; - Ok(auth) - } - - async fn join(self: Arc, user: User, lobby: LobbyId) -> Result { - let mu = self.users.get(&user).await?; - self.lobbies.get_mut(lobby, |mut l| l.join(&mu)).await - } - - async fn leave(self: Arc, user: User, lobby: LobbyId) -> Result<(), LobbyErr> { - let mu = self.users.get(&user).await?; - self.lobbies.get_mut(lobby, |mut l| l.leave(&mu)).await - } - - async fn ready( - self: Arc, - user: User, - lobby: LobbyId, - ready: bool, - ) -> Result { - self.lobbies - .get_mut(lobby, |mut l| l.ready(user, ready)) - .await - } - - /// A start request was received - async fn start_req( - self: Arc, - user: UserId, - lobby: LobbyId, - ) -> Result, LobbyErr> { - self.lobbies.get_mut(lobby, |mut l| l.start(user)).await?; - let lob = self.lobbies.consume(lobby).await?; - Ok(Utc::now()) - } - - async fn perform_action( - self: Arc, - user: User, - mtch: MatchId, - act: Action, - ) -> UpdateState { - unimplemented!() - } - - async fn leave_match(self: Arc, user: User, mtch: MatchId) -> Result<(), MatchErr> { - unimplemented!() - } -} diff --git a/games/rstnode/src/stats.rs b/games/rstnode/src/stats.rs deleted file mode 100644 index 1c651a98a0a8..000000000000 --- a/games/rstnode/src/stats.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! This file contains balancing data -//! -//! Each type of node and packet is being balanced for different -//! factors. The stats are just returned by these functions. -//! Whenever there is some effect that can happen in the game, it's -//! value (strength) will be derived from here. - -pub type Money = usize; - -/// The cost of doing business -pub mod costs { - use super::Money; - use crate::data::{Level, PacketType, Upgrade}; - - /// Takes the current node and desired upgrade level - pub fn upgrade(curr: &Upgrade, want: &Upgrade) -> Money { - use self::{Level::*, Upgrade::*}; - match (curr, want) { - // Base upgrades - (Base, Guard(One)) => 30, - (Base, Compute(One)) => 25, - (Base, Relay(One)) => 20, - - // Guards - (Guard(One), Guard(Two)) => 50, - (Guard(Two), Guard(Three)) => 75, - - // Compute is expensive - (Compute(One), Compute(Two)) => 50, - (Compute(Two), Compute(Three)) => 95, - - // Relays - (Relay(One), Relay(Two)) => 35, - (Relay(Two), Relay(Three)) => 55, - - // Can't touch this - (_, _) => unreachable!(), - } - } - - /// Sending certain packets costs money, let's find out how much - pub fn packets(node: &Upgrade, packet: &PacketType) -> Money { - use { - Level::*, - PacketType::*, - Upgrade::{Base, Compute as Cc, Guard, Relay}, - }; - match (node, packet) { - // Sending pings is free forever - (_, Ping) => 0, - // Capture packets always cost the same - (_, Capture) => 15, - - // The cost of compute packets increases with levels - // because their efficiency increases more dramatically - (Cc(One), Compute { .. }) => 7, - (Cc(Two), Compute { .. }) => 14, - (Cc(Three), Compute { .. }) => 21, - - // Payloads can only be sent from guards - (Guard(_), Payload { .. }) => 12, - - // Resets are relatively cheap - (Guard(One), Reset) => 16, - (Guard(Two), Reset) => 32, - (Guard(Three), Reset) => 48, - - (Guard(One), CNS) => 22, - (Guard(Two), CNS) => 40, - (Guard(Three), CNS) => 82, - - (Guard(Two), Nitm) => 64, - (Guard(Three), Nitm) => 148, - - (Guard(Two), Virus) => 40, - (Guard(Three), Virus) => 60, - - // Only level 3 guards can send takeovers - (Guard(Three), TakeOver) => 256, - - // Can't touch this - (_, _) => unreachable!(), - } - } -} - -/// This is what capitalists are all the rage about -pub mod gains { - use super::Money; - use crate::data::{Level, PacketType, Upgrade}; - use std::sync::atomic::Ordering; - - /// This will tell you if you'll receive any money this week - pub fn parse_packet(node: &Upgrade, packet: &PacketType) -> Money { - use { - Level::*, - PacketType::*, - Upgrade::{Base, Compute as Cc, Guard, Relay}, - }; - - /// A utility function which increments the packet progress - /// - /// Depending on the node that is processing the incoming - /// packet, progress is either stepped by 1, 1.5, or 2 times - /// the advertised step rate, which is set by the spawning - /// node of the compute job. This means that stronger compute - /// nodes have a positive impact even in mostly low-level - /// systems. - /// - /// This function returns if it reached or overflowed the max - fn incr_compute(l: &Level, packet: &PacketType) -> bool { - match (l, packet) { - (lvl, Compute { max, curr, step }) => { - if curr.load(Ordering::Relaxed) < *max { - curr.fetch_add( - match lvl { - One => *step, - Two => (*step as f32 * 1.5) as u16, - Three => *step * 2, - }, - Ordering::Relaxed, - ); - } - - // Did we reach the target? - curr.load(Ordering::Relaxed) >= *max - } - (_, _) => unreachable!(), - } - } - - match (node, packet) { - // A basic income for all node and packets - (Base, Ping) => 4, - (Guard(One), Ping) | (Cc(One), Ping) | (Relay(One), Ping) => 6, - (Guard(Two), Ping) | (Cc(Two), Ping) | (Relay(Two), Ping) => 11, - (Guard(Three), Ping) | (Cc(Three), Ping) | (Relay(Three), Ping) => 17, - - // A compute node will always increment the packet - // reference. If it made it to "max", it will then also - // return an amount of money that can be gained - (Cc(ref lvl), Compute { ref max, .. }) if incr_compute(lvl, packet) => match lvl { - One => *max as usize, - Two => (max * 2) as usize, - Three => (max * 3) as usize, - }, - - // If in doubt, nada! - (_, _) => 0, - } - } -} - -pub mod strengths { - use crate::data::Level::{self, *}; - - /// Determine the maximum amount of resources for a compute packet - pub fn compute_max(lvl: Level) -> u16 { - match lvl { - One => 65, - Two => 120, - Three => 250, - } - } - - /// Determine the step size by which a computation advances - pub fn compute_step(lvl: Level) -> u16 { - match lvl { - One => 7, - Two => 20, - Three => 32, - } - } - - /// Determine the reward gained from processing a payload packet - pub fn payload_reward(lvl: Level) -> u16 { - match lvl { - One => 70, - Two => 150, - Three => 275, - } - } -} diff --git a/games/rstnode/src/users.rs b/games/rstnode/src/users.rs deleted file mode 100644 index 0c93b83ec1da..000000000000 --- a/games/rstnode/src/users.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! A users abstraction module - -use crate::{ - wire::{LobbyErr, User, UserId}, - Id, -}; -use async_std::sync::{Arc, RwLock}; -use std::{ - collections::BTreeMap, - sync::atomic::{AtomicUsize, Ordering}, -}; - -pub struct MetaUser { - pub id: UserId, - pub name: String, - pub pw: String, - pub auth: User, -} - -pub struct UserStore { - max: AtomicUsize, - users: RwLock>>, -} - -impl UserStore { - /// Currently resuming a userstore isn't possible - pub fn new() -> Self { - UserStore { - max: 0.into(), - users: Default::default(), - } - } - - /// Get the metadata user for a login user - pub async fn get(&self, user: &User) -> Result, LobbyErr> { - match self.users.read().await.get(&user.id) { - Some(ref u) => Ok(Arc::clone(u)), - None => Err(LobbyErr::OtherError), - } - } - - pub async fn add>>( - &self, - name: String, - pw: S, - registered: bool, - ) -> (UserId, User) { - let id = self.max.fetch_add(1, Ordering::Relaxed); - let token = Id::random(); - let pw = pw.into().unwrap_or("".into()); - let auth = User { - id, - token, - registered, - }; - - self.users.write().await.insert( - id, - MetaUser { - id, - name, - pw, - auth: auth.clone(), - } - .into(), - ); - (id, auth.clone()) - } -} diff --git a/games/rstnode/src/wire/action.rs b/games/rstnode/src/wire/action.rs deleted file mode 100644 index 8b25e58649ba..000000000000 --- a/games/rstnode/src/wire/action.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::data::{NodeId, Upgrade}; -use serde::{Serialize, Deserialize}; - -/// All actions that a user can trigger via the UI -#[derive(Serialize, Deserialize)] -pub enum Action { - /// Cancel the running action - Cancel(NodeId), - /// Start a capture action - Capture { from: NodeId, to: NodeId }, - /// Set the compute targets - Compute { from: NodeId, to: Vec }, - /// Set to payload analysis mode - Payload(NodeId), - /// Send an exploit across the network - Reset { - from: NodeId, - to: NodeId, - exp: Exploit, - }, - /// Try to upgrade the node to a level - Upgrade { node: NodeId, level: Upgrade }, -} - -/// A type of exploit a node can start running -#[derive(Serialize, Deserialize)] -pub enum Exploit { - Reset, - CNS, - Nitm, - Virus, - TakeOver, -} diff --git a/games/rstnode/src/wire/mod.rs b/games/rstnode/src/wire/mod.rs deleted file mode 100644 index b1c231b44580..000000000000 --- a/games/rstnode/src/wire/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Network formats and container messages - -mod action; -pub use action::*; - -mod req; -pub use req::*; - -mod resp; -pub use resp::*; - -mod update; -pub use update::*; - -use crate::{ - data::{Color, Player}, - map::Map, - Id, -}; -use serde::{Deserialize, Serialize}; - -/// An alias for a User's ID -pub type UserId = usize; - -/// Represents a user payload -#[derive(Copy, Clone, Serialize, Deserialize)] -pub struct User { - /// The internal user ID - pub id: UserId, - /// The auth token provided by the client - pub token: Id, - /// Whether the scores will be tracked - pub registered: bool, -} - -/// A more lobby specific abstraction for a user -#[derive(Clone, Serialize, Deserialize)] -pub struct LobbyUser { - /// The user ID - pub id: UserId, - /// Their nick name - pub name: String, - /// Are they ready? - pub ready: bool, - /// Are they the lobby admin? - pub admin: bool, - /// The colour they will be in the match - pub color: Color, -} - -/// An alias for a Room ID -pub type LobbyId = usize; - -/// Represent a lobby -#[derive(Clone, Serialize, Deserialize)] -pub struct Lobby { - /// The ID of the lobby - pub id: LobbyId, - /// A set of user IDs - pub players: Vec, - /// The name of the map - pub map: String, - /// Settings - pub settings: Vec, -} - -/// An alias for a match ID -pub type MatchId = usize; - - diff --git a/games/rstnode/src/wire/req.rs b/games/rstnode/src/wire/req.rs deleted file mode 100644 index fba64287006b..000000000000 --- a/games/rstnode/src/wire/req.rs +++ /dev/null @@ -1,39 +0,0 @@ - -use super::{action::Action, User, LobbyId, MatchId}; -use serde::{Deserialize, Serialize}; - -/// A message sent from the game client to the server -#[derive(Serialize, Deserialize)] -pub enum Request { - /// Register yourself with the game server - Register(String, String), - - /// Login to your user session - /// - /// This user can't log into the system from another computer - Login(String, String), - - /// Close your user session - Logout(User), - - /// Start an anonymous session - Anonymous(String), - - /// A user joins a game lobby - Join(User, LobbyId), - - /// A user leaves a game lobby - Leave(User, LobbyId), - - /// Mark a user as ready - Ready(User, LobbyId, bool), - - /// Try to start the match - StartReq(User, LobbyId), - - /// Send a move in the game - GameAction(User, MatchId, Action), - - /// Leave the match (forfeit) - LeaveGame(User, MatchId), -} diff --git a/games/rstnode/src/wire/resp.rs b/games/rstnode/src/wire/resp.rs deleted file mode 100644 index 4812baf13e39..000000000000 --- a/games/rstnode/src/wire/resp.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Response values that the server can reply with - -use super::{Lobby, LobbyId, User, UserId}; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; - -/// A response values from the server -#[derive(Serialize, Deserialize)] -pub enum Response { - /// Response to the register request - Register(Result), - /// Response to login request - Login(Result), - /// Response to login request - Logout(Result<(), AuthErr>), - /// Get a list of available pairs - Rooms(Vec<(String, LobbyId)>), - /// A user joins a game lobby - Join(Result), - /// A user leaves a game lobby - Leave(Result<(), LobbyErr>), - /// Get the new set of ready states - Ready(LobbyUpdate), - /// Receiving a start request time - StartReq(DateTime), - /// Response to the action with the state update - GameUpdate(UpdateState), - /// Leave the match (forfeit) - LeaveGame(Result<(), MatchErr>), -} - -#[derive(Serialize, Deserialize)] -pub enum RegErr { - /// The password is way too bad - BadPassword, - /// THe username is already taken - UsernameTaken, - /// Other internal error, try again? - OtherError, -} - -#[derive(Serialize, Deserialize)] -pub enum AuthErr { - /// Wrong password for the user - WrongPassword, - /// The requested user doesn't exist - UserNotFound, - /// No session currently exists - NoSossion, - /// Other internal error, try again? - OtherError, -} - -#[derive(Serialize, Deserialize)] -pub enum LobbyErr { - /// The requested room is already full - RoomFull, - /// The room id is unknown - NoSuchRoom, - /// Previously not in room - NotInRoom, - /// Not everybody was ready - NotAllReady, - /// A request was sent by someone who isn't authorised - NotAuthorized, - /// Other internal error, try again? - OtherError, -} - -#[derive(Serialize, Deserialize)] -pub enum LobbyUpdate { - /// The set of ready users - Ready(Vec), -} - -/// The way the update was applied -#[derive(Serialize, Deserialize)] -pub enum UpdateState { - /// The update was applied seamlessly - Success, - /// The update was inserted, but had to be re-ordered with another update - Reordered, - /// The sent request was invalid and was not applied - Invalid, -} - -/// An error that can occur in a match -#[derive(Serialize, Deserialize)] -pub enum MatchErr { - /// The provided player wasn't in the match (anymore?) - NotInMatch, - /// The requested match had already ended - MatchAlreadyEnded -} diff --git a/games/rstnode/src/wire/update.rs b/games/rstnode/src/wire/update.rs deleted file mode 100644 index a1b47ff07e50..000000000000 --- a/games/rstnode/src/wire/update.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Update to the game state - -use super::UserId; -use crate::data::{NodeId, PacketId, Player, Upgrade}; -use serde::{Deserialize, Serialize}; - -/// An update provided by the game server -#[derive(Serialize, Deserialize)] -pub enum Update { - /// Update made to a node - Node(NodeUpdate), - /// Update made to a link - Link(LinkUpdate), - /// Update made to a packet - Packet(PacketUpdate), - /// Update made to the user set - User(UserUpdate), - /// An error occured, can be non-fatal - Error(UpdateError), -} - -/// Update made to a node -#[derive(Serialize, Deserialize)] -pub enum NodeUpdate { - /// The node owner changed - Owner(Player), - /// Represent a new upgrade state - Level { node: NodeId, new: Upgrade }, - /// A new packet was consumed from a link - NewPacket(PacketId), - /// Remove a packet from the node buffer - SentPacket(PacketId), - /// Dropped a packet - DropPacket(PacketId), -} - -/// Update made to a link -#[derive(Serialize, Deserialize)] -pub enum LinkUpdate { - /// Take a packet from a node's buffer - TakePacket(PacketId), - /// Give a packet to a node's buffer - GivePacket(PacketId), -} - -/// Update made to a packet -#[derive(Serialize, Deserialize)] -pub enum PacketUpdate { - /// Advance a packet along one step along the link - Increment(PacketId), -} - -/// Update made to the user set -#[derive(Serialize, Deserialize)] -pub enum UserUpdate { - UserLeft(UserId), -} - -/// An error occured, can be non-fatal -#[derive(Serialize, Deserialize)] -pub enum UpdateError { - /// You are the last user in the match - LastUser, - /// The game crashed, so kick all - GameCrashed, - /// The server's time was behind the client time - /// - /// This means that newer events will be dropped from the map - /// state. This should prompt the client to warn the user this - /// has happened, then resync the time of the game states. - TimeAheadServer, -} -- cgit v1.2.3