From 05f850b9c56757429eb041f067196f39bb9f36a8 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 21 Nov 2021 01:11:29 -0500 Subject: [PATCH] Modify character sheets to be label-based --- common/src/lib.rs | 8 - common/src/types.rs | 201 ----------------- common/src/types/character.rs | 394 ++++++++++++++++++++++++++++++++++ common/src/types/mod.rs | 14 ++ 4 files changed, 408 insertions(+), 209 deletions(-) delete mode 100644 common/src/types.rs create mode 100644 common/src/types/character.rs create mode 100644 common/src/types/mod.rs diff --git a/common/src/lib.rs b/common/src/lib.rs index 13fcf73..e496787 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,11 +2,3 @@ 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 deleted file mode 100644 index 4b418cf..0000000 --- a/common/src/types.rs +++ /dev/null @@ -1,201 +0,0 @@ -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/common/src/types/character.rs b/common/src/types/character.rs new file mode 100644 index 0000000..cb78ed9 --- /dev/null +++ b/common/src/types/character.rs @@ -0,0 +1,394 @@ +use crate::types::Roll; +use std::{collections::HashMap, iter::FromIterator}; +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +pub enum Error { + #[error("Tier out of range {0}")] + TierOutOfRange(u8), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum CharacterType { + Arkus, + Delve, + Glaive, + Jack { edge: PoolType }, + Nano, + Wright, +} + +impl From<&CharacterType> for String { + fn from(type_: &CharacterType) -> String { + match type_ { + CharacterType::Arkus => "Arkus", + CharacterType::Delve => "Delve", + CharacterType::Glaive => "Glaive", + CharacterType::Jack { .. } => "Jack", + CharacterType::Nano => "Nano", + CharacterType::Wright => "Wright", + } + .to_string() + } +} + +impl From for String { + fn from(type_: CharacterType) -> String { + String::from(&type_) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum PoolType { + Might, + Speed, + Intellect, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Cost { + Nothing, + Constant { stat: PoolType, cost: u8 }, + Variable(PoolType), +} + +#[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(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Pool { + pub current: u8, + pub max: u8, + pub edge: u8, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Pools { + pools: HashMap, +} + +impl Pools { + pub fn might(&self) -> &Pool { + self.pools.get(&PoolType::Might).unwrap() + } + pub fn speed(&self) -> &Pool { + self.pools.get(&PoolType::Speed).unwrap() + } + pub fn intellect(&self) -> &Pool { + self.pools.get(&PoolType::Intellect).unwrap() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct PointAllocation { + pub might: u8, + pub speed: u8, + pub intellect: u8, +} + +#[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)] +pub struct CharacterSheet { + name: String, + descriptor: String, + character_type: CharacterType, + focus: String, + initial_points: PointAllocation, + /* + tier: Tier, + effort: u8, + cypher_limit: u8, + special_abilities: Vec, + skills: Vec, + attacks: Vec, + defenses: Vec, + equipment: Vec, + cyphers: Vec, + */ +} + +impl CharacterSheet { + pub fn name(&self) -> String { + self.name.clone() + } + + pub fn description(&self) -> String { + let (first, _) = self.descriptor.split_at(1); + let first = first.to_lowercase(); + let article = + if first == "a" || first == "e" || first == "i" || first == "o" || first == "u" { + "an" + } else { + "a" + }; + format!( + "{} {} {} who {}", + article, + self.descriptor, + String::from(&self.character_type), + self.focus + ) + } + + pub fn pools(&self) -> Pools { + Pools { + pools: HashMap::from_iter( + vec![ + (PoolType::Might, self.might_pool()), + (PoolType::Speed, self.speed_pool()), + (PoolType::Intellect, self.intellect_pool()), + ] + .into_iter(), + ), + } + } + + fn might_pool(&self) -> Pool { + let (base, edge) = match self.character_type { + CharacterType::Glaive => (11, 1), + CharacterType::Jack { + edge: PoolType::Might, + } => (10, 1), + CharacterType::Jack { edge: _ } => (10, 0), + CharacterType::Nano => (7, 0), + CharacterType::Arkus => (8, 0), + CharacterType::Delve => (9, 0), + CharacterType::Wright => (9, 0), + }; + let max = base + self.initial_points.might; + let current = max; + + Pool { max, current, edge } + } + + fn speed_pool(&self) -> Pool { + let (base, edge) = match self.character_type { + CharacterType::Glaive => (10, 1), + CharacterType::Jack { + edge: PoolType::Speed, + } => (10, 1), + CharacterType::Jack { edge: _ } => (10, 0), + CharacterType::Nano => (9, 0), + CharacterType::Arkus => (9, 0), + CharacterType::Delve => (9, 1), + CharacterType::Wright => (7, 0), + }; + let max = base + self.initial_points.speed; + let current = max; + Pool { max, current, edge } + } + + fn intellect_pool(&self) -> Pool { + let (base, edge) = match self.character_type { + CharacterType::Glaive => (7, 0), + CharacterType::Jack { + edge: PoolType::Intellect, + } => (10, 1), + CharacterType::Jack { edge: _ } => (10, 0), + CharacterType::Nano => (12, 1), + CharacterType::Arkus => (11, 1), + CharacterType::Delve => (10, 1), + CharacterType::Wright => (12, 0), + }; + let max = base + self.initial_points.intellect; + let current = max; + + Pool { max, current, edge } + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct CypherTemplate { + name: String, + level: Roll, + form: String, + description: String, +} + +#[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::*; + + #[allow(dead_code)] + pub fn opal() -> CharacterSheet { + CharacterSheet { + name: "Opal".to_owned(), + descriptor: "Adaptable".to_owned(), + character_type: CharacterType::Jack { + edge: PoolType::Intellect, + }, + focus: "Speaks with a Silver Tongue".to_owned(), + initial_points: PointAllocation { + might: 1, + speed: 2, + intellect: 3, + }, + /* + pools: Pools { + might: Pool { + current: 11, + max: 11, + edge: 0, + }, + speed: Pool { + current: 12, + max: 12, + edge: 0, + }, + intellect: Pool { + current: 13, + max: 13, + edge: 1, + }, + }, + */ + /* + tier: Tier::new(1).unwrap(), + 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: PoolType::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(), + } + ], + */ + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn opals_character_sheet() { + let opal = super::test_data::opal(); + assert_eq!(opal.name(), "Opal"); + assert_eq!( + opal.description(), + "an Adaptable Jack who Speaks with a Silver Tongue" + ); + let pools = opal.pools(); + assert_eq!( + pools.might(), + &Pool { + current: 11, + max: 11, + edge: 0 + } + ); + + assert_eq!( + pools.speed(), + &Pool { + current: 12, + max: 12, + edge: 0 + } + ); + + assert_eq!( + pools.intellect(), + &Pool { + current: 13, + max: 13, + edge: 1 + } + ); + } +} diff --git a/common/src/types/mod.rs b/common/src/types/mod.rs new file mode 100644 index 0000000..01f979c --- /dev/null +++ b/common/src/types/mod.rs @@ -0,0 +1,14 @@ +mod character; + +pub use character::CharacterSheet; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Die { + sides: u8, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Roll { + dice: Vec, + modifier: i8, +}