Construct a basic character with some descriptors and focuses
This commit is contained in:
parent
9a3676d696
commit
0588a803cf
|
@ -25,31 +25,38 @@ pub enum CharacterTypeEdge {
|
|||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CharacterType {
|
||||
text: String,
|
||||
name: String,
|
||||
starting_pools: HashMap<PoolType, u8>,
|
||||
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<PoolType, u8>);
|
||||
|
||||
#[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<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)]
|
||||
pub struct CharacterSheet {
|
||||
name: String,
|
||||
descriptor: String,
|
||||
descriptor: Descriptor,
|
||||
character_type: CharacterType,
|
||||
focus: String,
|
||||
focus: Focus,
|
||||
flex_edge: Option<PoolType>,
|
||||
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<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 {
|
||||
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 {
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue