From f2b9e6ccb3588aea75a4f4570ffd777cc7f63866 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 30 Sep 2021 23:44:26 -0400 Subject: [PATCH] Base data structures --- .envrc | 1 + .gitignore | 1 + common/Cargo.toml | 11 +++ common/src/lib.rs | 12 +++ common/src/types.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++ docs/stories.md | 25 ++++++ server/Cargo.toml | 10 +++ server/src/main.rs | 46 ++++++++++ shell.nix | 20 +++++ 9 files changed, 327 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 common/Cargo.toml create mode 100644 common/src/lib.rs create mode 100644 common/src/types.rs create mode 100644 docs/stories.md create mode 100644 server/Cargo.toml create mode 100644 server/src/main.rs create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4a4726a --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use_nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b60de5b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/target diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..53742b8 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1" } +serde_derive = { version = "1" } +thiserror = { version = "1" } diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..13fcf73 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,12 @@ +#[macro_use] +extern crate serde_derive; + +pub mod types; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/common/src/types.rs b/common/src/types.rs new file mode 100644 index 0000000..4b418cf --- /dev/null +++ b/common/src/types.rs @@ -0,0 +1,201 @@ +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +pub enum Error { + #[error("Tier out of range {0}")] + TierOutOfRange(u8), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Type { + Arkus, + Delve, + Glaive, + Jack, + Nano, + Wright, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum StatType { + Might, + Speed, + Intellect, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Cost { + Nothing, + Constant { stat: StatType, cost: u8 }, + Variable(StatType), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Tier(u8); + +impl Tier { + pub fn new(val: u8) -> Result { + if val < 1 || val > 6 { + Err(Error::TierOutOfRange(val)) + } else { + Ok(Tier(val)) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Stat { + current: u8, + max: u8, + edge: u8, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Stats { + might: Stat, + speed: Stat, + intellect: Stat, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SpecialAbility { + name: String, + cost: Cost, + description: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SkillRank { + Inability, + Trained, + Specialized, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Skill { + skill: String, + rank: SkillRank, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Range { + Immediate, + Short, + Long, + VeryLong, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Attack { + weapon: String, + damage: u8, + range: Range, + ammo: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Defense { + armor: String, + defense: u8, + speed_cost: u8, +} + +#[derive(Debug, Serialize, Deserialize)] +struct CharacterSheet { + name: String, + descriptor: String, + type_: Type, + focus: String, + tier: Tier, + pools: Stats, + effort: u8, + cypher_limit: u8, + special_abilities: Vec, + skills: Vec, + attacks: Vec, + defenses: Vec, + equipment: Vec, + cyphers: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Cypher { + name: String, + level: u8, + form: String, + description: String, +} + +#[cfg(any(test, feature = "test_data"))] +mod test_data { + use super::*; + + fn opal() -> CharacterSheet { + CharacterSheet { + name: "Opal".to_owned(), + descriptor: "Adaptable".to_owned(), + type_: Type::Jack, + focus: "Speaks with a Silver Tongue".to_owned(), + tier: Tier::new(1).unwrap(), + pools: Stats { + might: Stat { + current: 11, + max: 11, + edge: 0, + }, + speed: Stat { + current: 12, + max: 12, + edge: 0, + }, + intellect: Stat { + current: 13, + max: 13, + edge: 1, + }, + }, + effort: 1, + cypher_limit: 3, + special_abilities: vec![ + SpecialAbility { name: "Versatile".to_owned(), + cost: Cost::Nothing, + description: "+2 to any pool. It can be reassigned after each ten-hour recovery roll.".to_owned() }, + SpecialAbility { name: "Flex Skill".to_owned(), + cost: Cost::Nothing, + description: "at the beginning of each day, choose one skill (other than attack or defense). For the rest of the day, you are trained in that skill.".to_owned() }, + SpecialAbility{ name: "Face Morph".to_owned(), + cost: Cost::Constant { stat: StatType::Intellect, cost: 2 }, + description: "You alter your features and coloration for one hour.".to_owned() }, + ], + skills: vec![ + Skill{ skill: "Perception".to_owned(), rank: SkillRank::Trained }, + Skill{ skill: "Resilient".to_owned(), rank: SkillRank::Trained }, + Skill{ skill: "Social interactions".to_owned(), rank: SkillRank::Trained }, + Skill{ skill: "Pleasant social interactiosn".to_owned(), rank: SkillRank::Specialized }, + ], + attacks: vec![ + Attack { weapon: "Crank Crossbow".to_owned(), damage: 4, range: Range::Long, ammo: Some(25), }, + Attack { weapon: "Rapier".to_owned(), damage: 2, range: Range::Immediate, ammo: None, }, + ], + defenses: vec![ + Defense { + armor: "Brigandine".to_owned(), + defense: 2, + speed_cost: 2, + } + ], + equipment: vec![ + "A book for recording favorite words, inspiration stories, and speech anecdotes.".to_owned(), + "Explorer's pack".to_owned(), + ], + cyphers: vec![ + Cypher { + name: "Flying Cap".to_owned(), + level: 3, + form: "Hat".to_owned(), + description: "Whatever".to_owned(), + } + ], + } + } +} diff --git a/docs/stories.md b/docs/stories.md new file mode 100644 index 0000000..dd3a448 --- /dev/null +++ b/docs/stories.md @@ -0,0 +1,25 @@ +# Tasks + +- Players + - need to be able to see their character sheets + - need to be able to edit their character sheets + - need to record damage + - need to mark abilities as used + - need to see active status effects ("flex skill") + - need to trigger recovery (regain pool points, reset skills that reset on recovery) + - need to mark artifacts as depleted + - need to expend cyphers +- GM + - needs to see all character sheet + - needs to send items to players + - needs a pool of potential cyphers +- Other + - all actions must be undoable + +Cypher: +- name +- level +- form +- description + +Character Sheet diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..b9ef565 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "datasphere-server" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1", features = ["full"] } +warp = { version = "0.3.1" } diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..b1b1321 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,46 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use warp::Filter; + +fn gm_paths() -> impl Filter { + let base = warp::path("gm").and(warp::header("authentication")); + + let character_sheet = base + .clone() + .and(warp::path!("character" / String)) + .map(|auth: String, name| format!("name: {}", name)); + let send_item = base + .clone() + .and(warp::path("send_item")) + .map(|auth| format!("send_item")); + let send_resource = base + .clone() + .and(warp::path("send_resource")) + .map(|auth| format!("send_resource")); + + character_sheet.or(send_item).or(send_resource) +} + +fn player_paths() -> impl Filter { + let base = warp::path("player").and(warp::header("authentication")); + let character_sheet = base + .and(warp::path!("character" / String)) + .map(|authentication: String, name: String| format!("name: {}", name)); + + character_sheet +} + +#[tokio::main] +pub async fn main() { + let hi = warp::path!("hello" / String) + .and(warp::header("user-agent")) + .map(|param: String, agent: String| format!("Saluton! {}, {}", param, agent)); + let bye = warp::path!("goodbye").map(|| "Goodbye!"); + + let server = warp::serve(hi.or(bye)); + server + .run(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 8000, + )) + .await; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..29db582 --- /dev/null +++ b/shell.nix @@ -0,0 +1,20 @@ +let + rust_overlay = import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"); + pkgs = import { overlays = [ rust_overlay ]; }; + unstable = import {}; + rust = pkgs.rust-bin.stable."1.54.0".default.override { + extensions = [ "rust-src" ]; + }; + +in pkgs.mkShell { + name = "datasphere"; + + nativeBuildInputs = [ + rust + unstable.rust-analyzer + ]; + + shellHook = '' + if [ -e ~/.nixpkgs/shellhook.sh ]; then . ~/.nixpkgs/shellhook.sh; fi + ''; +}