Construct a basic character with some descriptors and focuses

This commit is contained in:
Savanni D'Gerinel 2022-03-05 16:14:29 -05:00
parent 9a3676d696
commit 0588a803cf
2 changed files with 313 additions and 65 deletions

View File

@ -25,31 +25,38 @@ pub enum CharacterTypeEdge {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct CharacterType { pub struct CharacterType {
text: String, name: String,
starting_pools: HashMap<PoolType, u8>, starting_pools: HashMap<PoolType, u8>,
edge: CharacterTypeEdge, edge: CharacterTypeEdge,
} }
impl CharacterType { impl CharacterType {
fn pool(&self, type_: PoolType) -> u8 { fn pool(&self, type_: &PoolType) -> u8 {
self.starting_pools.get(&type_).unwrap().clone() self.starting_pools.get(type_).unwrap().clone()
} }
fn edge(&self, type_: PoolType) -> u8 { fn edge(&self, type_: &PoolType) -> u8 {
match self.edge { match self.edge {
CharacterTypeEdge::Flexible => 0, 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 { pub enum Cost {
Nothing, Nothing,
Constant { stat: PoolType, cost: u8 }, Constant { stat: PoolType, cost: u8 },
Variable(PoolType), Variable(PoolType),
} }
impl Default for Cost {
fn default() -> Cost {
Cost::Nothing
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Tier(u8); pub struct Tier(u8);
@ -88,33 +95,32 @@ impl Pools {
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PointAllocation { pub struct PointAllocation(HashMap<PoolType, u8>);
pub might: u8,
pub speed: u8,
pub intellect: u8,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SpecialAbility { pub struct SpecialAbility {
name: String, name: String,
#[serde(default)]
cost: Cost, cost: Cost,
description: String, description: String,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum SkillRank { #[serde(rename_all = "lowercase")]
pub enum SkillLevel {
Inability, Inability,
Trained, Trained,
Specialized, Specialized,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Skill { pub struct Skill {
skill: String, name: String,
rank: SkillRank, level: SkillLevel,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Range { pub enum Range {
Immediate, Immediate,
Short, Short,
@ -137,12 +143,47 @@ pub struct Defense {
speed_cost: u8, 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<DescriptorAbility>,
}
#[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<FocusAbility>,
tier2: Vec<FocusAbility>,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct CharacterSheet { pub struct CharacterSheet {
name: String, name: String,
descriptor: String, descriptor: Descriptor,
character_type: CharacterType, character_type: CharacterType,
focus: String, focus: Focus,
flex_edge: Option<PoolType>,
initial_points: PointAllocation, initial_points: PointAllocation,
/* /*
tier: Tier, tier: Tier,
@ -163,7 +204,7 @@ impl CharacterSheet {
} }
pub fn description(&self) -> String { 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 first = first.to_lowercase();
let article = let article =
if first == "a" || first == "e" || first == "i" || first == "o" || first == "u" { if first == "a" || first == "e" || first == "i" || first == "o" || first == "u" {
@ -172,11 +213,12 @@ impl CharacterSheet {
"a" "a"
}; };
format!( format!(
"{} {} {:?} who {}", "{} {} {} who {}",
article, self.descriptor, self.character_type, self.focus article, self.descriptor.name, self.character_type.name, self.focus.name
) )
} }
/*
pub fn pools(&self) -> Pools { pub fn pools(&self) -> Pools {
Pools { Pools {
pools: HashMap::from_iter( 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 { Pool {
current: 0, current: max,
max: 0, max,
edge: 0, 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 { fn speed_pool(&self) -> Pool {
let max = self.character_type.pool(PoolType::Speed) + self.initial_points.speed;
Pool { Pool {
current: 0, current: max,
max: 0, max,
edge: 0, edge: self.edge(PoolType::Speed),
} }
} }
fn intellect_pool(&self) -> Pool { fn intellect_pool(&self) -> Pool {
let max = self.character_type.pool(PoolType::Intellect) + self.initial_points.intellect;
Pool { Pool {
current: 0, current: max,
max: 0, max,
edge: 0, edge: self.edge(PoolType::Intellect),
} }
} }
*/
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -236,7 +307,7 @@ mod test_data {
use super::*; use super::*;
#[allow(dead_code)] #[allow(dead_code)]
pub const GLAIVE_DESCRIPTOR: &str = "text: Glaive pub const GLAIVE: &str = "name: Glaive
starting_pools: starting_pools:
might: 11 might: 11
speed: 10 speed: 10
@ -245,7 +316,7 @@ edge:
static: static:
might: 1 might: 1
speed: 1"; speed: 1";
pub const JACK_DESCRIPTOR: &str = "text: Jack pub const JACK: &str = "name: Jack
starting_pools: starting_pools:
might: 10 might: 10
speed: 10 speed: 10
@ -253,19 +324,152 @@ starting_pools:
edge: flexible edge: flexible
"; ";
pub fn jack() -> CharacterType {
let mut starting_pools: HashMap<PoolType, u8> = 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 { pub fn opal() -> CharacterSheet {
let mut initial_points: HashMap<PoolType, u8> = HashMap::new();
initial_points.insert(PoolType::Might, 1);
initial_points.insert(PoolType::Speed, 2);
initial_points.insert(PoolType::Intellect, 3);
CharacterSheet { CharacterSheet {
name: "Opal".to_owned(), name: "Opal".to_owned(),
descriptor: "Adaptable".to_owned(), descriptor: adaptable(),
character_type: CharacterType::Jack { character_type: jack(),
edge: PoolType::Intellect, focus: silver_tongue(),
}, flex_edge: Some(PoolType::Intellect),
focus: "Speaks with a Silver Tongue".to_owned(), initial_points: PointAllocation(initial_points),
initial_points: PointAllocation {
might: 1,
speed: 2,
intellect: 3,
},
} }
} }
} }
@ -273,26 +477,75 @@ edge: flexible
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::test_data::*; use super::test_data::*;
use super::*;
use serde_yaml; use serde_yaml;
#[test] #[test]
fn parses_character_type() { fn parses_character_type() {
let glaive: CharacterType = let glaive: CharacterType = serde_yaml::from_str(GLAIVE).expect("should deserialize");
serde_yaml::from_str(GLAIVE_DESCRIPTOR).expect("should deserialize"); assert_eq!(glaive.name, "Glaive");
assert_eq!(glaive.text, "Glaive"); assert_eq!(glaive.pool(&PoolType::Might), 11);
assert_eq!(glaive.pool(PoolType::Might), 11); assert_eq!(glaive.edge(&PoolType::Might), 1);
assert_eq!(glaive.edge(PoolType::Might), 1); assert_eq!(glaive.edge(&PoolType::Intellect), 0);
assert_eq!(glaive.edge(PoolType::Intellect), 0);
} }
#[test] #[test]
fn parses_character_type_with_flexible_edge() { fn parses_character_type_with_flexible_edge() {
let jack: CharacterType = let jack: CharacterType = serde_yaml::from_str(JACK).expect("should deserialize");
serde_yaml::from_str(JACK_DESCRIPTOR).expect("should deserialize"); assert_eq!(jack.name, "Jack");
assert_eq!(jack.text, "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] #[test]
fn opals_character_sheet() { fn opals_character_sheet() {
@ -302,10 +555,9 @@ mod test {
opal.description(), opal.description(),
"an Adaptable Jack who Speaks with a Silver Tongue" "an Adaptable Jack who Speaks with a Silver Tongue"
); );
let pools = opal.pools();
assert_eq!( assert_eq!(
pools.might(), opal.pool(&PoolType::Might),
&Pool { Pool {
current: 11, current: 11,
max: 11, max: 11,
edge: 0 edge: 0
@ -313,8 +565,8 @@ mod test {
); );
assert_eq!( assert_eq!(
pools.speed(), opal.pool(&PoolType::Speed),
&Pool { Pool {
current: 12, current: 12,
max: 12, max: 12,
edge: 0 edge: 0
@ -322,8 +574,8 @@ mod test {
); );
assert_eq!( assert_eq!(
pools.intellect(), opal.pool(&PoolType::Intellect),
&Pool { Pool {
current: 13, current: 13,
max: 13, max: 13,
edge: 1 edge: 1

View File

@ -21,8 +21,4 @@ in pkgs.mkShell {
rust rust
unstable.rust-analyzer unstable.rust-analyzer
]; ];
shellHook = ''
if [ -e ~/.nixpkgs/shellhook.sh ]; then . ~/.nixpkgs/shellhook.sh; fi
'';
} }