diff --git a/common/src/types/character.rs b/common/src/types/character.rs index fb7162c..6a3f33c 100644 --- a/common/src/types/character.rs +++ b/common/src/types/character.rs @@ -25,31 +25,38 @@ pub enum CharacterTypeEdge { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct CharacterType { - text: String, + name: String, starting_pools: HashMap, edge: CharacterTypeEdge, } impl CharacterType { - fn pool(&self, type_: PoolType) -> u8 { - self.starting_pools.get(&type_).unwrap().clone() + fn pool(&self, type_: &PoolType) -> u8 { + self.starting_pools.get(type_).unwrap().clone() } - fn edge(&self, type_: PoolType) -> u8 { + fn edge(&self, type_: &PoolType) -> u8 { match self.edge { CharacterTypeEdge::Flexible => 0, - CharacterTypeEdge::Static(ref edge) => edge.get(&type_).unwrap_or(&0).clone(), + CharacterTypeEdge::Static(ref edge) => edge.get(type_).unwrap_or(&0).clone(), } } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] pub enum Cost { Nothing, Constant { stat: PoolType, cost: u8 }, Variable(PoolType), } +impl Default for Cost { + fn default() -> Cost { + Cost::Nothing + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct Tier(u8); @@ -88,33 +95,32 @@ impl Pools { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct PointAllocation { - pub might: u8, - pub speed: u8, - pub intellect: u8, -} +pub struct PointAllocation(HashMap); -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct SpecialAbility { name: String, + #[serde(default)] cost: Cost, description: String, } -#[derive(Debug, Serialize, Deserialize)] -pub enum SkillRank { +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SkillLevel { Inability, Trained, Specialized, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Skill { - skill: String, - rank: SkillRank, + name: String, + level: SkillLevel, } #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum Range { Immediate, Short, @@ -137,12 +143,47 @@ pub struct Defense { speed_cost: u8, } +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct Enabler { + name: String, + descriptor: String, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum DescriptorAbility { + Skill(Skill), + Enabler(Enabler), + SpecialAbility(SpecialAbility), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Descriptor { + name: String, + effects: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub struct FocusAbility { + name: String, + effect: DescriptorAbility, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct Focus { + name: String, + tier1: Vec, + tier2: Vec, +} + #[derive(Debug, Serialize, Deserialize)] pub struct CharacterSheet { name: String, - descriptor: String, + descriptor: Descriptor, character_type: CharacterType, - focus: String, + focus: Focus, + flex_edge: Option, initial_points: PointAllocation, /* tier: Tier, @@ -163,7 +204,7 @@ impl CharacterSheet { } pub fn description(&self) -> String { - let (first, _) = self.descriptor.split_at(1); + let (first, _) = self.descriptor.name.split_at(1); let first = first.to_lowercase(); let article = if first == "a" || first == "e" || first == "i" || first == "o" || first == "u" { @@ -172,11 +213,12 @@ impl CharacterSheet { "a" }; format!( - "{} {} {:?} who {}", - article, self.descriptor, self.character_type, self.focus + "{} {} {} who {}", + article, self.descriptor.name, self.character_type.name, self.focus.name ) } + /* pub fn pools(&self) -> Pools { Pools { pools: HashMap::from_iter( @@ -189,30 +231,59 @@ impl CharacterSheet { ), } } + */ - fn might_pool(&self) -> Pool { + fn pool(&self, pool: &PoolType) -> Pool { + let max = + self.character_type.pool(pool) + self.initial_points.0.get(pool).cloned().unwrap_or(0); Pool { - current: 0, - max: 0, - edge: 0, + current: max, + max, + edge: self.edge(pool), + } + } + + fn edge(&self, pool: &PoolType) -> u8 { + match self.character_type.edge { + CharacterTypeEdge::Static(ref edges) => edges.get(pool).cloned().unwrap_or(0).clone(), + CharacterTypeEdge::Flexible => { + if self.flex_edge == Some(pool.clone()) { + 1 + } else { + 0 + } + } + } + } + + /* + fn might_pool(&self) -> Pool { + + Pool { + current: max, + max, + edge: self.edge(PoolType::Might), } } fn speed_pool(&self) -> Pool { + let max = self.character_type.pool(PoolType::Speed) + self.initial_points.speed; Pool { - current: 0, - max: 0, - edge: 0, + current: max, + max, + edge: self.edge(PoolType::Speed), } } fn intellect_pool(&self) -> Pool { + let max = self.character_type.pool(PoolType::Intellect) + self.initial_points.intellect; Pool { - current: 0, - max: 0, - edge: 0, + current: max, + max, + edge: self.edge(PoolType::Intellect), } } + */ } #[derive(Debug, Serialize, Deserialize)] @@ -236,7 +307,7 @@ mod test_data { use super::*; #[allow(dead_code)] - pub const GLAIVE_DESCRIPTOR: &str = "text: Glaive + pub const GLAIVE: &str = "name: Glaive starting_pools: might: 11 speed: 10 @@ -245,7 +316,7 @@ edge: static: might: 1 speed: 1"; - pub const JACK_DESCRIPTOR: &str = "text: Jack + pub const JACK: &str = "name: Jack starting_pools: might: 10 speed: 10 @@ -253,19 +324,152 @@ starting_pools: edge: flexible "; + pub fn jack() -> CharacterType { + let mut starting_pools: HashMap = HashMap::new(); + starting_pools.insert(PoolType::Might, 10); + starting_pools.insert(PoolType::Speed, 10); + starting_pools.insert(PoolType::Intellect, 10); + CharacterType { + name: "Jack".to_owned(), + starting_pools, + edge: CharacterTypeEdge::Flexible, + } + } + + pub const ADAPTABLE: &str = " +name: Adaptable +effects: + - specialability: + name: Versatile + description: +2 to any pool, changeable every ten hours + - skill: + name: Pleasant Social Interactions + level: trained + - skill: + name: Overcoming deprivation, sorrow, or pain + level: trained +"; + + pub fn adaptable() -> Descriptor { + Descriptor { + name: "Adaptable".to_owned(), + effects: vec![ + DescriptorAbility::SpecialAbility(versatile()), + DescriptorAbility::Skill(pleasant_social()), + DescriptorAbility::Skill(overcoming_adversity()), + ], + } + } + + pub fn overcoming_adversity() -> Skill { + Skill { + name: "Overcoming deprivation, sorrow, or pain".to_owned(), + level: SkillLevel::Trained, + } + } + + pub const VERSATILE: &str = " +name: Versatile +description: +2 to any pool, changeable every ten hours + "; + + pub fn versatile() -> SpecialAbility { + SpecialAbility { + name: "Versatile".to_owned(), + cost: Cost::Nothing, + description: "+2 to any pool, changeable every ten hours".to_owned(), + } + } + + pub const ENTERTAINS: &str = "name: Entertains +tier1: + - name: Levity + text: Some text which describes what the player gets + action: enabler +"; + + pub const EXPLORES_DARK_PLACES: &str = "name: Explores Dark Places +tier1: + - name: Superb Explorer + skills: + - skill: searching + level: trained + - skill: listening + level: trained +"; + + pub const FOCUSES_MIND_OVER_MATTER: &str = "name: Focuses Mind Over Matter +tier1: + - name: Deflect Attacks + cost: { type: \"Constant\", stat: \"Intellect\", cost: 1 } + text: Trained in speed defense for ten minutes +"; + + pub const SILVER_TONGUE: &str = " +name: Speaks with a Silver Tongue +equipment: + - Book of favorite stories +tier1: + - name: Poetic License + skills: + - name: all social interactions + level: trained +tier2: + - name: A Smile and a Word + text: free level of effort on interaction tasks +"; + + pub fn silver_tongue() -> Focus { + Focus { + name: "Speaks with a Silver Tongue".to_owned(), + tier1: vec![FocusAbility { + name: "Poetic License".to_owned(), + effect: DescriptorAbility::Skill(Skill { + name: "all social interactions".to_owned(), + level: SkillLevel::Trained, + }), + }], + tier2: vec![FocusAbility { + name: "A Smile and a Word".to_owned(), + effect: DescriptorAbility::Enabler(Enabler { + name: "".to_owned(), + descriptor: "free level of effort on any interaction task".to_owned(), + }), + }], + } + } + + pub const DEFLECT_ATTACKS: &str = " +name: Deflect Attacks +cost: { constant: { stat: \"intellect\", cost: 1 } } +description: Trained in speed defense for ten minutes +"; + + pub const PLEASANT_SOCIAL: &str = " +name: Pleasant social interactions +level: trained +"; + + pub fn pleasant_social() -> Skill { + Skill { + name: "Pleasant social interactions".to_owned(), + level: SkillLevel::Trained, + } + } + pub fn opal() -> CharacterSheet { + let mut initial_points: HashMap = HashMap::new(); + initial_points.insert(PoolType::Might, 1); + initial_points.insert(PoolType::Speed, 2); + initial_points.insert(PoolType::Intellect, 3); + 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, - }, + descriptor: adaptable(), + character_type: jack(), + focus: silver_tongue(), + flex_edge: Some(PoolType::Intellect), + initial_points: PointAllocation(initial_points), } } } @@ -273,26 +477,75 @@ edge: flexible #[cfg(test)] mod test { use super::test_data::*; + use super::*; use serde_yaml; #[test] fn parses_character_type() { - let glaive: CharacterType = - serde_yaml::from_str(GLAIVE_DESCRIPTOR).expect("should deserialize"); - assert_eq!(glaive.text, "Glaive"); - assert_eq!(glaive.pool(PoolType::Might), 11); - assert_eq!(glaive.edge(PoolType::Might), 1); - assert_eq!(glaive.edge(PoolType::Intellect), 0); + let glaive: CharacterType = serde_yaml::from_str(GLAIVE).expect("should deserialize"); + assert_eq!(glaive.name, "Glaive"); + assert_eq!(glaive.pool(&PoolType::Might), 11); + assert_eq!(glaive.edge(&PoolType::Might), 1); + assert_eq!(glaive.edge(&PoolType::Intellect), 0); } #[test] fn parses_character_type_with_flexible_edge() { - let jack: CharacterType = - serde_yaml::from_str(JACK_DESCRIPTOR).expect("should deserialize"); - assert_eq!(jack.text, "Jack"); + let jack: CharacterType = serde_yaml::from_str(JACK).expect("should deserialize"); + assert_eq!(jack.name, "Jack"); } - use super::*; + #[test] + fn parses_skill() { + let skill: Skill = serde_yaml::from_str(PLEASANT_SOCIAL).expect("should deserialize"); + assert_eq!(skill, pleasant_social()); + } + + #[test] + fn parses_special_ability() { + let special: SpecialAbility = serde_yaml::from_str(VERSATILE).expect("should deserialize"); + assert_eq!(special, versatile()); + } + + #[test] + fn parses_special_ability_with_cost() { + let deflect = SpecialAbility { + name: "Deflect Attacks".to_owned(), + cost: Cost::Constant { + stat: PoolType::Intellect, + cost: 1, + }, + description: "Trained in speed defense for ten minutes".to_owned(), + }; + println!("SPECIAL: {}", serde_yaml::to_string(&deflect).unwrap()); + let ability: SpecialAbility = + serde_yaml::from_str(DEFLECT_ATTACKS).expect("should deserialize"); + assert_eq!(ability, deflect); + } + + #[test] + fn parses_adaptable_descriptor() { + let descriptor: Descriptor = serde_yaml::from_str(ADAPTABLE).expect("should deserialize"); + assert_eq!(descriptor.effects.len(), 3); + assert_eq!( + descriptor.effects[0], + DescriptorAbility::SpecialAbility(versatile()) + ); + } + + /* + #[test] + fn parses_enabler_focus() { + let entertains: Focus = serde_yaml::from_str(ENTERTAINS).expect("should deserialize"); + assert_eq!( + entertains.tier1, + DescriptorAbility::Enabler(Enabler { + name: "Levity".to_owned(), + text: "Some text which describes what the player gets".to_owned(), + }) + ); + } + */ #[test] fn opals_character_sheet() { @@ -302,10 +555,9 @@ mod test { opal.description(), "an Adaptable Jack who Speaks with a Silver Tongue" ); - let pools = opal.pools(); assert_eq!( - pools.might(), - &Pool { + opal.pool(&PoolType::Might), + Pool { current: 11, max: 11, edge: 0 @@ -313,8 +565,8 @@ mod test { ); assert_eq!( - pools.speed(), - &Pool { + opal.pool(&PoolType::Speed), + Pool { current: 12, max: 12, edge: 0 @@ -322,8 +574,8 @@ mod test { ); assert_eq!( - pools.intellect(), - &Pool { + opal.pool(&PoolType::Intellect), + Pool { current: 13, max: 13, edge: 1 diff --git a/shell.nix b/shell.nix index 5d95ee9..056d12d 100644 --- a/shell.nix +++ b/shell.nix @@ -21,8 +21,4 @@ in pkgs.mkShell { rust unstable.rust-analyzer ]; - - shellHook = '' - if [ -e ~/.nixpkgs/shellhook.sh ]; then . ~/.nixpkgs/shellhook.sh; fi - ''; }