Modify character sheets to be label-based

This commit is contained in:
Savanni D'Gerinel 2021-11-21 01:11:29 -05:00
parent 460b24d96e
commit b685057346
4 changed files with 408 additions and 209 deletions

View File

@ -2,11 +2,3 @@
extern crate serde_derive;
pub mod types;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@ -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<Tier, Error> {
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<u8>,
}
#[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<SpecialAbility>,
skills: Vec<Skill>,
attacks: Vec<Attack>,
defenses: Vec<Defense>,
equipment: Vec<String>,
cyphers: Vec<Cypher>,
}
#[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(),
}
],
}
}
}

View File

@ -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<CharacterType> 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<Tier, Error> {
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<PoolType, Pool>,
}
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<u8>,
}
#[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<SpecialAbility>,
skills: Vec<Skill>,
attacks: Vec<Attack>,
defenses: Vec<Defense>,
equipment: Vec<String>,
cyphers: Vec<Cypher>,
*/
}
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
}
);
}
}

14
common/src/types/mod.rs Normal file
View File

@ -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<Die>,
modifier: i8,
}