Modify character sheets to be label-based
This commit is contained in:
parent
f2b9e6ccb3
commit
05f850b9c5
|
@ -2,11 +2,3 @@
|
|||
extern crate serde_derive;
|
||||
|
||||
pub mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
Loading…
Reference in New Issue